This commit is contained in:
Misaka_Company
2026-01-29 17:05:28 +08:00
parent c9cf84ed4d
commit 2b9ba2de9c
19 changed files with 0 additions and 9531 deletions

View File

@@ -1,987 +0,0 @@
' ========================================
' 类模块: 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 方法
' 功能: 从[平台配置清单]工作表加载BOM数据并构建数据结构
' 参数:
' wsPlatform - [平台配置清单]工作表对象,包含完整的物料信息和类别配置
' 表结构: 行号|模块|代号|名称|数量|选择条件|备注|类别|上层类别|类别选用条件|66代码
' 数据从第4行开始(前3行是标题)
' 处理步骤:
' 1. 从[平台配置清单]一次性加载所有物料信息和类别信息
' 2. 建立类别的父子关系,构建层级树
'
' 重要说明:
' - 所有数据都从[平台配置清单]读取,不再需要[领料配置]表
' - "类别"
' - "类别选用条件"()
' - "66代码"66使
' ========================================
Public Sub LoadData(wsPlatform As Worksheet)
Dim i As Long, lastRow As Long
Dim mat As clsMaterialItem
Dim cat As clsCategory
' ========================================
' 第一步: 从平台配置清单加载所有物料和类别信息
' 说明:
' 读取列说明:
' - C列(代号)、D列(名称)、E列(数量)、F列(选择条件)
' - H列(类别)、I列(上层类别)、J列(类别选用条件)、K列(66代码)
' - 从第4行开始读取(前3行是标题)
'
' 处理逻辑:
' 1. 所有物料都存入dictAllMaterials字典(包括无类别的物料)
' 2. "类别"
' 3. 类别对象保存CategorySelectCondition属性
' 4. 物料对象保存CategorySelectCondition和Code66属性
' ========================================
lastRow = wsPlatform.Cells(wsPlatform.Rows.Count, "C").End(xlUp).row
For i = 4 To lastRow ' 从第4行开始(跳过标题)
' 1.
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 & "") ' 选择条件
' 2.
Dim catName As String
Dim parentCatName As String
Dim catSelectCond As String
Dim code66 As String
catName = Trim(wsPlatform.Cells(i, "H").value & "") ' 类别
parentCatName = Trim(wsPlatform.Cells(i, "I").value & "") ' 上层类别
catSelectCond = Trim(wsPlatform.Cells(i, "J").value & "") ' 类别选用条件
code66 = Trim(wsPlatform.Cells(i, "K").value & "") ' 66代码
' 3. 保存类别相关属性到物料对象
mat.Category = catName
mat.ParentCategory = parentCatName
mat.CategorySelectCondition = catSelectCond
mat.Code66 = code66
' 4. (,)
If mat.code <> "" Then
Set dictAllMaterials(mat.code) = mat
End If
' 5. ,,
If catName <> "" Then
' 确保类别对象存在(如果类别不存在则创建)
If Not dictCategories.Exists(catName) Then
Set cat = New clsCategory
cat.categoryName = catName
cat.ParentCategoryName = parentCatName
cat.CategorySelectCondition = catSelectCond ' ⭐ 设置类别选用条件
Set dictCategories(catName) = cat
End If
' 将物料添加到类别
dictCategories(catName).AddMaterial mat
End If
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
' ========================================
' GetRequiredCategories 方法 (私有)
' 功能: 根据型号条件确定哪些类别是需要的
' 参数:
' conditions - 从型号提取的条件字典 (Dictionary对象)
' 包含如 lcfw, gclj, jycz 等条件变量
' 返回: Collection对象, 包含所有需要的 clsCategory 对象
'
' 判断逻辑:
' 遍历所有类别,对每个类别调用 IsRequiredForModel(conditions) 方法:
' - 如果类别选用条件为空 → 该类别总是需要,添加到结果集
' - 如果类别选用条件不为空 → 用 conditions 匹配
' - 匹配成功 → 添加到结果集
' - 匹配失败 → 不添加
'
' 示例:
' 假设型号条件为: lcfw=M16, gclj=M20
'
' 1: CategorySelectCondition = ""
' → IsRequiredForModel = True → 添加到结果集
'
' 2: CategorySelectCondition = "lcfw=M02"
' → IsRequiredForModel = False → 不添加
'
' 3: CategorySelectCondition = "gclj=M20"
' → IsRequiredForModel = True → 添加到结果集
'
' 用途:
' 在 GetValidMaterialsByModel 中,只检查需要的类别是否有物料匹配
' 不需要的类别不参与完整性检查
' ========================================
Private Function GetRequiredCategories(conditions As Object) As collection
Dim result As collection
Set result = New collection
' 遍历所有类别
Dim key As Variant
For Each key In dictCategories.Keys
Dim cat As clsCategory
Set cat = dictCategories(key)
' 检查该类别是否需要
If cat.IsRequiredForModel(conditions) Then
result.Add cat
End If
Next key
Set GetRequiredCategories = 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,
' (3) "MissingCategories" - Collection,
'
' ⭐ 完整性判断规则(重构后):
' 第一步: 确定哪些类别是该型号需要的
' - ,"类别选用条件"
' - 类别选用条件为空 → 该类别总是需要
' - 类别选用条件不为空 → 用型号条件匹配,匹配成功才需要
'
' 第二步: 只检查需要的类别的完整性
' - 对于每个需要的类别(顶层类别或其需要领取的子类别)
' - 必须有且仅有一个物料被匹配
' - 如果某个需要的类别有0个或多于1个物料,则视为不完整
' - 不需要的类别不参与完整性检查
'
' 示例:
' "YTHN-100.A0.532.M203.M16.Y3" (M16, M20)
'
' 1: CategorySelectCondition = ""
' 2: CategorySelectCondition = "lcfw=M02" (M16)
' 3: CategorySelectCondition = "gclj=M20" ()
'
' 只有类别1和类别3需要检查完整性,类别2不检查
'
' 调用示例:
' Dim result As Collection
' Set result = bomMgr.GetValidMaterialsByModel("YTHN-100.A0.532.M203.M16.Y3")
' Dim materials As Collection
' Dim isComplete As Boolean
' Dim missingCats As Collection
' Set materials = result("Materials")
' isComplete = result("IsComplete")
' Set missingCats = result("MissingCategories")
' ========================================
Public Function GetValidMaterialsByModel(modelStr As String) As collection
Dim result As New collection
Dim allMaterials As New collection
Dim allMissingCats 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"
result.Add allMissingCats, "MissingCategories"
Set GetValidMaterialsByModel = result
Exit Function
End If
Dim extractor As New clsConditionExtractor
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
' 条件匹配器
Dim matcher As New clsConditionMatcher
' ⭐ 第一步: 获取需要的类别列表
Dim requiredCats As collection
Set requiredCats = GetRequiredCategories(conditions)
' ⭐ 第二步: 只检查需要的根类别的完整性
' 说明: 不检查所有需要的类别,而是只检查根类别
' 因为 CheckCategoryCompleteness 会递归检查子类别
' 如果检查所有类别(包括子类别),会导致重复检查和重复添加到 missingCats
isComplete = True
Dim reqCat As clsCategory
Dim i As Long
For i = 1 To requiredCats.Count
Set reqCat = requiredCats(i)
' ⭐ 关键修正: 只检查根类别(无父类别的类别)
' "部件""接头""弹性元件"
'
If reqCat.ParentCategoryName = "" Then
' 检查该根类别及其所有子类别的完整性
Dim catResult As Object
Set catResult = CheckCategoryCompleteness(reqCat, matcher, conditions)
' 1.
Dim mat As clsMaterialItem
Dim matCollection As collection
Set matCollection = catResult("Materials")
For Each mat In matCollection
allMaterials.Add mat
Next mat
' 2.
Dim missingCollection As collection
Set missingCollection = catResult("Missing")
Dim missingCatName As Variant
For Each missingCatName In missingCollection
allMissingCats.Add missingCatName
Next missingCatName
' 3.
If Not catResult("IsComplete") Then
isComplete = False
End If
End If ' If reqCat.ParentCategoryName = ""
Next i
'
result.Add allMaterials, "Materials"
result.Add isComplete, "IsComplete"
result.Add allMissingCats, "MissingCategories"
Set GetValidMaterialsByModel = result
End Function
' ========================================
' CheckCategoryCompleteness 方法 (私有)
' 功能: 检查单个类别的完整性(递归处理子类别)
' 参数:
' cat - 类别对象
' matcher - 条件匹配器
' conditions - 提取的条件字典
' 返回: Dictionary对象
' "Materials" - Collection,
' "IsComplete" - Boolean,
' "Missing" - Collection,
'
' 完整性判断逻辑:
' 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 missingCats 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: 叶子类别(无子类别)
' ========================================
If matchCount = 1 Then
isComplete = True
Else
isComplete = False
' 如果是叶子节点且没有匹配到物料,记录该类别为缺失
If matchCount = 0 Then
missingCats.Add cat.categoryName
End If
' : matchCount > 1 ()"Missing"
End If
Else
' ========================================
' 情况2: 有子类别
' 完整性判断规则:
' - 方式一: 父类别有1个匹配物料 → 完整
' - 方式二: 父类别无匹配物料, 但所有子类别都完整 → 完整
' - 其他情况 → 不完整
' ========================================
If matchCount = 1 Then
' 方式一: 父类别有且仅有1个物料, 使用父类别 -> 完整
isComplete = True
' 不需要检查子类别了missingCats 保持为空
ElseIf matchCount = 0 Then
' 方式二: 父类别无匹配物料, 必须降级检查所有子类别
' 清空当前物料集合(确保没东西), 准备收集子类别结果
Set materials = New collection
isComplete = True ' 先假设完整, 若任一子类别不完整则置错
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)
' a)
Dim subMaterials As collection
Set subMaterials = subResult("Materials")
Dim k As Long
For k = 1 To subMaterials.Count
materials.Add subMaterials(k)
Next k
' b)
Dim subMissing As collection
Set subMissing = subResult("Missing")
Dim item As Variant
For Each item In subMissing
missingCats.Add item
Next item
' c)
If Not subResult("IsComplete") Then
isComplete = False
End If
Next j
' ⭐ 关键修正: 如果所有子类别都完整, 父类别不应该被标记为缺失
' 只有当父类别是叶子类别且没有物料时, 才应该被标记为缺失
' 对于有子类别的父类别, 只要所有子类别都完整, 父类别就是完整的
Else
' 父类别有 >1 个匹配, 视为不完整(冲突)
isComplete = False
' 父类别有多个匹配, 这种情况下不应该标记为缺失(而是配置错误)
End If
End If
'
Set result("Materials") = materials
result("IsComplete") = isComplete
Set result("Missing") = missingCats
Set CheckCategoryCompleteness = result
End Function

View File

@@ -1,84 +0,0 @@
' ========================================
' 类模块: 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 ' 是否叶子类别(无子类别)
Public CategorySelectCondition As String ' 类别选用条件(用于判断该类别是否需要)
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
' ========================================
' IsRequiredForModel 方法
' 功能: 根据型号条件判断该类别是否需要
' 参数:
' conditions - 从型号提取的条件字典 (Dictionary对象)
' 返回:
' True - 该类别需要
' False - 该类别不需要
'
' 判断逻辑:
' 1. 如果 CategorySelectCondition 为空字符串
' → 该类别总是需要(无条件限制)
' 2. 如果 CategorySelectCondition 不为空
' → 使用 clsConditionMatcher 匹配条件
' → 匹配成功 → 需要
' → 匹配失败 → 不需要
'
' 示例:
' 假设型号条件为: lcfw=M16, gclj=M20
'
' 1: CategorySelectCondition = ""
' → IsRequiredForModel(conditions) = True
'
' 2: CategorySelectCondition = "lcfw=M16"
' → IsRequiredForModel(conditions) = True (匹配)
'
' 3: CategorySelectCondition = "lcfw=M02"
' → IsRequiredForModel(conditions) = False (不匹配)
'
' 4: CategorySelectCondition = "gclj=M20 AND jycz=1"
' → IsRequiredForModel(conditions) = True (匹配)
' ========================================
Public Function IsRequiredForModel(conditions As Object) As Boolean
If Me.CategorySelectCondition = "" Then
' 选用条件为空,该类别总是需要
IsRequiredForModel = True
Else
' conditions CategorySelectCondition
Dim matcher As New clsConditionMatcher
IsRequiredForModel = matcher.IsMatch(Me.CategorySelectCondition, conditions)
End If
End Function

View File

@@ -1,243 +0,0 @@
' ========================================
' 类模块: 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")
' 规则4: 安装形式 (azxs)
' InstallForm A0=, Z0=
m_ExtractionRules("azxs") = Array("InstallForm", "Direct")
' 规则5: 表壳形式 (bkxs)
' 从 ShellForm 中直接提取3位代码前2位表壳类型+后1位罩壳类型
' 532 = 53(304) + 2()
m_ExtractionRules("bkxs") = Array("ShellForm", "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

View File

@@ -1,250 +0,0 @@
' ========================================
' 类模块: 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

View File

@@ -1,14 +0,0 @@
' ========================================
' 类模块: 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 ' 上层类别
Public CategorySelectCondition As String ' 类别选用条件(用于判断该类别是否需要)
Public Code66 As String ' 66

View File

@@ -1,34 +0,0 @@
' ========================================
' 类模块: clsMaterialWithFlag
' 用途: 包装物料对象及其匹配状态标志
' ========================================
Option Explicit
Private mMaterial As clsMaterialItem ' 物料对象
Private mIsComplete As Boolean ' 是否完整匹配
' ========================================
' 属性: Material
' 说明: 获取或设置物料对象
' ========================================
Public Property Get Material() As clsMaterialItem
Set Material = mMaterial
End Property
Public Property Set Material(ByVal value As clsMaterialItem)
Set mMaterial = value
End Property
' ========================================
' 属性: IsComplete
' 说明: 获取或设置是否完整匹配
' True: 该物料属于完整的物料清单
' False: 该物料属于不完整的物料清单(某些类别缺失物料)
' ========================================
Public Property Get isComplete() As Boolean
isComplete = mIsComplete
End Property
Public Property Let isComplete(ByVal value As Boolean)
mIsComplete = value
End Property

View File

@@ -1,229 +0,0 @@
' ========================================
' 类模块: 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

View File

@@ -1,178 +0,0 @@
' ========================================
' 模块: modBOMProcessor
' 用途: BOM处理和操作示例
' ⭐ v2.0 更新:所有过程使用新的 LoadData(wsPlatform) 接口
' ========================================
Option Explicit
Sub TestBOMStructure()
' 初始化BOM管理器
Dim bomMgr As New clsBOMManager
' 加载数据
Dim wsPlatform As Worksheet
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' ⭐ 使用新接口
bomMgr.LoadData 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 wsPlatform As Worksheet
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' ⭐ 使用新接口
bomMgr.LoadData 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 wsPlatform As Worksheet
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' ⭐ 使用新接口
bomMgr.LoadData 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 wsPlatform As Worksheet
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' ⭐ 使用新接口
bomMgr.LoadData 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

View File

@@ -1,986 +0,0 @@
' ========================================
' 模块: modModelParserExamples
' 用途: 型号解析与物料匹配的实际应用示例
' ========================================
Option Explicit
' ========================================
' 示例1: 根据型号生成完整的领料清单
' ⭐ v2.0 更新:使用新的 LoadData(wsPlatform) 接口
' ========================================
Sub Example1_GeneratePickingListByModel()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
' 加载配置
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData 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: 批量处理多个型号
' ⭐ v2.0 更新:使用新的 LoadData(wsPlatform) 接口
' ========================================
Sub Example2_BatchProcessModels()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
' 加载配置
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData 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: 验证物料选择条件的有效性
' ⭐ v2.0 更新:使用新的 LoadData(wsPlatform) 接口
' ========================================
Sub Example5_ValidateMaterialConditions()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
' 加载配置
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData 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: 对比启用/禁用自动降级的效果
' ⭐ v2.0 更新:使用新的 LoadData(wsPlatform) 接口
' ========================================
Sub Example6_CompareAutoFallback()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
' 加载配置
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData 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方法 (使用内置的缺失列表)
' ⭐ v2.0 更新:使用新的 LoadData(wsPlatform) 接口
' 完整性检查使用"类别选用条件"动态判断需要的类别
' ========================================
Sub Example7_TestGetValidMaterialsByModel()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
' 加载配置
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData 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.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)
' 1. 调用方法获取结果
Dim result As collection
Set result = bomMgr.GetValidMaterialsByModel(modelStr)
Dim materials As collection
Dim missingCats As collection
Dim isComplete As Boolean
Set materials = result("Materials")
Set missingCats = result("MissingCategories") ' 直接获取缺失列表
isComplete = result("IsComplete")
' 2. 写入头部信息
wsTest.Cells(row, 1).value = "测试 " & (i + 1) & ": " & testName
wsTest.Cells(row, 1).Font.Bold = True
wsTest.Cells(row, 1).Interior.Color = RGB(240, 240, 240)
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
' 3. 打印匹配到的物料
wsTest.Cells(row, 1).value = "【已匹配物料】"
wsTest.Cells(row, 1).Font.Bold = True
row = row + 1
wsTest.Cells(row, 1).value = "类别"
wsTest.Cells(row, 2).value = "代号"
wsTest.Cells(row, 3).value = "名称"
wsTest.Cells(row, 4).value = "数量"
wsTest.Range(wsTest.Cells(row, 1), wsTest.Cells(row, 4)).Font.Bold = True
wsTest.Range(wsTest.Cells(row, 1), wsTest.Cells(row, 4)).Interior.Color = RGB(220, 220, 220)
row = row + 1
Dim mat As clsMaterialItem
If materials.Count > 0 Then
For Each mat In materials
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
row = row + 1
Next mat
Else
wsTest.Cells(row, 1).value = "(无)"
row = row + 1
End If
' 4. 打印缺失的类别 (新增核心功能)
If missingCats.Count > 0 Then
row = row + 1
wsTest.Cells(row, 1).value = "【缺失类别】"
wsTest.Cells(row, 1).Font.Color = RGB(255, 0, 0)
wsTest.Cells(row, 1).Font.Bold = True
row = row + 1
Dim catName As Variant
For Each catName In missingCats
wsTest.Cells(row, 1).value = "?? " & catName
wsTest.Cells(row, 1).Font.Color = RGB(255, 0, 0)
wsTest.Cells(row, 2).value = "未找到匹配物料"
row = row + 1
Next catName
End If
' 添加分隔行
row = row + 1
wsTest.Cells(row, 1).value = String(80, "-")
row = row + 2
Next i
wsTest.Columns("A:D").AutoFit
MsgBox "测试完成!", vbInformation
End Sub
' ========================================
' 示例8: 批量处理产品订单,生成物料清单
' ⭐ v2.0 更新:使用新的 LoadData(wsPlatform) 接口
' ========================================
Sub Example8_BatchProcessOrders()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
Dim wsOrders As Worksheet
Dim wsOutput As Worksheet
' 加载配置
On Error Resume Next
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
Set wsOrders = ThisWorkbook.Worksheets("产品订单")
If wsPlatform Is Nothing Then
MsgBox "找不到工作表: 平台配置清单", vbCritical
Exit Sub
End If
If wsOrders Is Nothing Then
MsgBox "找不到工作表: 产品订单", vbCritical
Exit Sub
End If
On Error GoTo 0
bomMgr.LoadData wsPlatform
' 读取订单数据
Dim lastRow As Long
lastRow = wsOrders.Cells(wsOrders.Rows.Count, "A").End(xlUp).row
If lastRow < 2 Then
MsgBox "产品订单工作表没有数据!" & vbCrLf & _
"请确保第一行是表头,从第二行开始是数据。", vbExclamation
Exit Sub
End If
' 使用 Collection 收集所有输出行
Dim outputData As collection
Set outputData = New collection
' 添加表头
outputData.Add Array("来源单号", "产品型号", "物料代码", "物料名称", "数量", "选择条件", "类别")
' 遍历每个订单,收集数据
Dim i As Long
Dim orderNo As String
Dim modelStr As String
Dim materials As collection
Dim mat As clsMaterialItem
Dim processedCount As Long
Dim materialCount As Long
processedCount = 0
materialCount = 0
For i = 2 To lastRow
orderNo = Trim(wsOrders.Cells(i, 1).value)
modelStr = Trim(wsOrders.Cells(i, 2).value)
' 跳过空行
If orderNo = "" And modelStr = "" Then
GoTo ContinueLoop
End If
' 验证必填字段
If orderNo = "" Then
orderNo = "(未填写)"
End If
If modelStr = "" Then
outputData.Add Array(orderNo, "(空白型号)", "错误", "产品型号为空", "", "", "")
GoTo ContinueLoop
End If
' 获取物料
On Error Resume Next
Set materials = bomMgr.GetMaterialsByModel(modelStr, "", True)
On Error GoTo 0
If materials Is Nothing Then
outputData.Add Array(orderNo, modelStr, "错误", "无法解析型号或获取物料", "", "", "")
GoTo ContinueLoop
End If
' 收集物料明细
If materials.Count > 0 Then
For Each mat In materials
outputData.Add Array( _
orderNo, _
modelStr, _
mat.code, _
mat.Name, _
mat.Quantity, _
IIf(mat.Condition = "", "(无条件)", mat.Condition), _
mat.Category _
)
materialCount = materialCount + 1
Next mat
Else
outputData.Add Array(orderNo, modelStr, "(无)", "未找到任何匹配物料", "", "", "")
End If
processedCount = processedCount + 1
ContinueLoop:
Next i
' 创建输出工作表
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Worksheets("产品订单物料清单").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set wsOutput = ThisWorkbook.Worksheets.Add
wsOutput.Name = "产品订单物料清单"
' 批量写入数据到工作表
If outputData.Count > 0 Then
Dim dataArray() As Variant
ReDim dataArray(1 To outputData.Count, 1 To 7)
Dim j As Long
Dim rowArr As Variant
For j = 1 To outputData.Count
rowArr = outputData(j)
dataArray(j, 1) = rowArr(0)
dataArray(j, 2) = rowArr(1)
dataArray(j, 3) = rowArr(2) ' 这里是物料代码
dataArray(j, 4) = rowArr(3)
dataArray(j, 5) = rowArr(4)
dataArray(j, 6) = rowArr(5)
dataArray(j, 7) = rowArr(6)
Next j
' --- 关键修改:在写入数据前,将 C 列设置为文本格式 ---
' 使用 NumberFormat = "@" 强制指定为文本格式
wsOutput.Columns("C").NumberFormat = "@"
' 一次性写入
wsOutput.Range("A1").Resize(outputData.Count, 7).value = dataArray
' 格式化表头
With wsOutput.Range("A1:G1")
.Font.Bold = True
.Interior.Color = RGB(200, 200, 200)
' 表头所在的 C1 单元格通常可以改回常规格式,或者保持文本格式也不影响
.NumberFormat = "General"
End With
End If
' 格式化
wsOutput.Columns("A:G").AutoFit
' 显示统计信息
Dim msg As String
msg = "批量处理完成!" & vbCrLf & vbCrLf
msg = msg & "处理订单数: " & processedCount & vbCrLf
msg = msg & "生成物料记录: " & materialCount & " 条" & vbCrLf & vbCrLf
msg = msg & "请查看工作表: 产品订单物料清单"
MsgBox msg, vbInformation
End Sub
' ========================================
' 示例9: 批量处理订单 - 带完整性检查与模块数量校验
' 功能:
' 1. 提取型号条件
' 2. 校验模块数量如果是3个模块则报错
' 3. 校验类别完整性(如果缺失类别则报错,使用类别选用条件判断)
' 4. 生成BOM清单
'
' ⭐ v2.0 更新:
' - 移除 [领料配置] 表依赖
' - 使用新的 LoadData(wsPlatform) 接口(单参数)
' - 完整性检查使用"类别选用条件"动态判断需要的类别
' ========================================
Sub Example9_BatchProcessOrders_WithCheck()
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet, wsOrders As Worksheet, wsOutput As Worksheet
Dim parser As clsModelParser
Dim extractor As clsConditionExtractor
' 1. 初始化与加载数据
On Error Resume Next
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
Set wsOrders = ThisWorkbook.Worksheets("产品订单")
On Error GoTo 0
If wsPlatform Is Nothing Or wsOrders Is Nothing Then
MsgBox "错误:缺少必要的工作表 (平台配置清单/产品订单)", vbCritical
Exit Sub
End If
' ⭐ 使用新接口:只需要一个参数
bomMgr.LoadData wsPlatform
' 2. 准备输出容器
Dim outputData As collection
Set outputData = New collection
' 添加表头
outputData.Add Array("来源单号", "产品型号", "物料代码", "物料名称", "数量", "选择条件", "提取的物料选择条件", "类别", "备注")
' 3. 遍历订单
Dim lastRow As Long
lastRow = wsOrders.Cells(wsOrders.Rows.Count, "A").End(xlUp).row
Dim i As Long
Dim orderNo As String, modelStr As String
Dim extractedCondStr As String
Dim parts() As String
Dim moduleCount As Integer
Dim result As collection, materials As collection, missingCats As collection
Dim isComplete As Boolean
Dim mat As clsMaterialItem
' 辅助变量
Dim key As Variant
Dim missingStr As String
Dim catItem As Variant
For i = 2 To lastRow
orderNo = Trim(wsOrders.Cells(i, 1).value)
modelStr = Trim(wsOrders.Cells(i, 2).value)
If orderNo = "" And modelStr = "" Then GoTo NextOrder
If orderNo = "" Then orderNo = "(未填写)"
' -------------------------------------------------
' 步骤 A: 解析型号并提取条件字符串 (用于输出列)
' -------------------------------------------------
extractedCondStr = ""
Set parser = New clsModelParser
Set extractor = New clsConditionExtractor
If parser.ParseModel(modelStr) Then
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
For Each key In conditions.Keys
extractedCondStr = extractedCondStr & key & "=" & conditions(key) & "; "
Next key
If Len(extractedCondStr) > 2 Then extractedCondStr = Left(extractedCondStr, Len(extractedCondStr) - 2)
Else
' 解析失败直接输出错误
outputData.Add Array(orderNo, modelStr, "", "", "", "", "", "", "解析失败: " & parser.ErrorMessage)
GoTo NextOrder
End If
' -------------------------------------------------
' 步骤 B: 校验模块数量 (表头 | 表盘 | 附件 | 法兰)
' -------------------------------------------------
' Split返回0-based数组。0=1个模块, 1=2个模块, 2=3个模块
parts = Split(modelStr, "|")
moduleCount = UBound(parts) + 1
' 规则:如果出现三个模块,不输出物料,备注填入原因
If moduleCount = 3 Then
outputData.Add Array(orderNo, modelStr, "", "", "", "", extractedCondStr, "", "包含其它模块")
GoTo NextOrder
End If
' -------------------------------------------------
' 步骤 C: 获取物料并校验类别完整性
' -------------------------------------------------
Set result = bomMgr.GetValidMaterialsByModel(modelStr)
isComplete = result("IsComplete")
Set materials = result("Materials")
Set missingCats = result("MissingCategories")
' 规则:如果类别缺失,不输出物料,备注填入缺失的类别
If Not isComplete Then
missingStr = ""
For Each catItem In missingCats
missingStr = missingStr & catItem & ", "
Next catItem
If Len(missingStr) > 2 Then missingStr = Left(missingStr, Len(missingStr) - 2)
If missingStr = "" Then missingStr = "完整性校验未通过(未知原因)" ' 防御性编程
outputData.Add Array(orderNo, modelStr, "", "", "", "", extractedCondStr, "", "类别缺失: " & missingStr)
GoTo NextOrder
End If
' -------------------------------------------------
' 步骤 D: 正常输出物料
' -------------------------------------------------
If materials.Count > 0 Then
For Each mat In materials
outputData.Add Array( _
orderNo, _
modelStr, _
mat.code, _
mat.Name, _
mat.Quantity, _
IIf(mat.Condition = "", "(无条件)", mat.Condition), _
extractedCondStr, _
mat.Category, _
"" _
)
Next mat
Else
outputData.Add Array(orderNo, modelStr, "", "", "", "", extractedCondStr, "", "无匹配物料")
End If
NextOrder:
Next i
' 4. 写入结果到新工作表
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_校验版"
If outputData.Count > 0 Then
' 转换为二维数组以提高写入速度
Dim dataArr() As Variant
ReDim dataArr(1 To outputData.Count, 1 To 9)
Dim r As Long, c As Long
Dim rowItem As Variant
For r = 1 To outputData.Count
rowItem = outputData(r)
For c = 0 To 8
dataArr(r, c + 1) = rowItem(c)
Next c
Next r
' 设置物料代码列(C列)为文本格式
wsOutput.Columns("C:C").NumberFormat = "@"
' 写入数据
wsOutput.Range("A1").Resize(outputData.Count, 9).value = dataArr
' 格式化美化
With wsOutput.Range("A1:I1")
.Font.Bold = True
.Interior.Color = RGB(220, 230, 241)
.HorizontalAlignment = xlCenter
End With
wsOutput.Columns("A:I").AutoFit
' 备注列标红显示
wsOutput.Columns("I:I").Font.Color = RGB(255, 0, 0)
wsOutput.Cells(1, 9).Font.Color = RGB(0, 0, 0) ' 表头改回黑色
End If
MsgBox "处理完成!请查看工作表 '订单BOM_校验版'。", vbInformation
End Sub

View File

@@ -1,307 +0,0 @@
' ========================================
' 模块: 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 wsPlatform As Worksheet
On Error Resume Next
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
On Error GoTo 0
If wsPlatform Is Nothing Then
Debug.Print "? 错误: 找不到必需的工作表 [平台配置清单]"
Exit Sub
End If
' ⭐ 使用新接口
bomMgr.LoadData 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 wsPlatform As Worksheet
On Error Resume Next
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
On Error GoTo 0
If wsPlatform Is Nothing Then
Debug.Print "? 错误: 找不到必需的工作表 [平台配置清单]"
Exit Sub
End If
' ⭐ 使用新接口
bomMgr.LoadData 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

View File

@@ -1,464 +0,0 @@
# Example1_GeneratePickingListByModel 过程时序图
## 过程说明
本时序图展示 `Example1_GeneratePickingListByModel` 过程的详细执行流程,该过程根据产品型号生成完整的领料清单。
---
## 1. 简略版时序图(概览)
此版本展示主要流程和关键交互,适合快速了解整体架构。
```mermaid
sequenceDiagram
autonumber
participant Main as Example1_Sub
participant BOMMgr as clsBOMManager
participant Excel as Excel工作表
participant PickList as 型号领料清单表
participant Parser as 型号解析组件
participant DataStore as 数据集合
%% ==================== 初始化 ====================
Note over Main: 1. 初始化与加载数据
Main->>BOMMgr: 创建并初始化 BOMManager
Main->>Excel: 获取配置工作表<br/>(领料配置、平台配置清单)
Main->>BOMMgr: LoadData(wsConfig, wsPlatform)
activate BOMMgr
BOMMgr->>Excel: 读取类别层级和物料数据
BOMMgr->>DataStore: 构建内存数据结构<br/>(dictCategories, dictAllMaterials)
deactivate BOMMgr
%% ==================== 创建输出表 ====================
Note over Main: 2. 创建输出工作表
Main->>Main: modelStr = "YTHN-100.A0.532.M203.M16.Y3|..."
Main->>Excel: 删除旧工作表(如存在)
Main->>Excel: 创建新工作表 "型号领料清单"
Excel-->>Main: PickList 工作表对象
%% ==================== 解析型号 ====================
Note over Main: 3. 解析型号并提取条件
Main->>PickList: 写入产品型号标题
Main->>Parser: ParseModelAndExtractConditions(modelStr)
activate Parser
Parser->>Parser: 解析表头型号(YTHN-100...)<br/>解析表盘型号(BP-095...)
Parser->>DataStore: 提取选择条件<br/>(gclj, jycz, lcfw)
Parser-->>Main: Conditions Dictionary
deactivate Parser
Main->>PickList: 写入提取条件到工作表
%% ==================== 写入表头 ====================
Note over Main: 4. 写入数据表头
Main->>PickList: 写入列标题<br/>(类别, 代号, 名称, 数量, 选择条件, 匹配状态)
%% ==================== 处理物料 ====================
Note over Main: 5. 遍历类别并匹配物料
Main->>BOMMgr: GetRootCategories()
BOMMgr-->>Main: 根类别集合
loop 每个根类别
Main->>BOMMgr: GetMaterialsByModel(modelStr, categoryName)
activate BOMMgr
BOMMgr->>DataStore: 获取类别对象
BOMMgr->>Parser: 匹配物料选择条件
Parser-->>BOMMgr: 匹配结果集合
BOMMgr-->>Main: Materials Collection
deactivate BOMMgr
Note over Main: 写入该类别的所有物料
loop 每个匹配的物料
Main->>PickList: 写入物料详细信息<br/>(代号, 名称, 数量, 条件)
end
end
%% ==================== 完成输出 ====================
Note over Main: 6. 格式化并完成
Main->>PickList: AutoFit 格式化
Main->>Main: 显示完成消息框
```
**简略版特点**
- **6个参与者**相比详细版的11个
- **6个主要阶段**相比详细版的14个步骤
- **合并了内部细节**:解析器、提取器、匹配器的内部操作合并为一个组件
- **简化循环展示**:不展示条件判断、分支逻辑等细节
- **适合**:快速理解整体流程和架构
---
## 2. 详细版时序图(完整流程)
此版本展示每个方法的调用细节、内部逻辑和数据流向,适合深入理解实现。
## 时序图
```mermaid
sequenceDiagram
autonumber
participant Main as Example1_Sub
participant BOMMgr as clsBOMManager
participant Workbooks as Excel.Worksheets
participant ConfigWS as 领料配置表
participant PlatformWS as 平台配置清单表
participant Parser as clsModelParser
participant Extractor as clsConditionExtractor
participant Matcher as clsConditionMatcher
participant PickListWS as 型号领料清单表
participant RootCats as 根类别Collection
participant Cat as clsCategory
participant Materials as 物料Collection
participant Conditions as 条件Dictionary
%% ==================== 初始化阶段 ====================
Note over Main: 1. 初始化 BOM 管理器
Main->>BOMMgr: New clsBOMManager
activate BOMMgr
BOMMgr->>BOMMgr: 初始化 dictCategories<br/>初始化 dictAllMaterials<br/>初始化 rootCategories
deactivate BOMMgr
%% ==================== 加载配置阶段 ====================
Note over Main: 2. 获取工作表对象
Main->>Workbooks: Worksheets("领料配置")
Workbooks-->>Main: ConfigWS
Main->>Workbooks: Worksheets("平台配置清单")
Workbooks-->>Main: PlatformWS
Note over Main: 3. 加载 BOM 数据
Main->>BOMMgr: LoadData(ConfigWS, PlatformWS)
activate BOMMgr
BOMMgr->>ConfigWS: 读取类别配置数据<br/>第2行起物料代号、类别名称、上层类别
ConfigWS-->>BOMMgr: 类别层级数据
BOMMgr->>BOMMgr: 构建类别树结构<br/>创建 clsCategory 对象<br/>建立父子关系
BOMMgr->>PlatformWS: 读取物料清单数据<br/>第4行起
PlatformWS-->>BOMMgr: 完整物料数据
BOMMgr->>BOMMgr: 创建 clsMaterialItem 对象<br/>填充 dictAllMaterials 字典
BOMMgr->>BOMMgr: 将物料分配到对应类别<br/>建立 rootCategories 集合
deactivate BOMMgr
%% ==================== 准备型号数据 ====================
Note over Main: 4. 定义产品型号
Main->>Main: modelStr = "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
%% ==================== 创建输出工作表 ====================
Note over Main: 5. 删除旧的领料清单表
Main->>Workbooks: Worksheets("型号领料清单")
alt 工作表存在
Workbooks-->>Main: 工作表对象
Main->>PickListWS: Delete
else 工作表不存在
Workbooks-->>Main: 错误
Main->>Main: On Error Resume Next (忽略错误)
end
Note over Main: 6. 创建新工作表
Main->>Workbooks: Worksheets.Add()
Workbooks-->>Main: PickListWS
Main->>PickListWS: Name = "型号领料清单"
%% ==================== 写入标题行 ====================
Note over Main: 7. 写入产品型号
Main->>Main: row = 1
Main->>PickListWS: Cells(row, 1).value = "产品型号"
Main->>PickListWS: Cells(row, 2).value = modelStr
Main->>Main: row = row + 1
%% ==================== 解析型号并提取条件 ====================
Note over Main: 8. 解析型号提取条件
Main->>BOMMgr: ParseModelAndExtractConditions(modelStr)
activate BOMMgr
BOMMgr->>Parser: New clsModelParser
activate Parser
BOMMgr->>Parser: ParseModel(modelStr)
Note over Parser: 解析表头型号
Parser->>Parser: 提取型号前缀: "YTHN"
Parser->>Parser: 提取公称外径: "100"
Parser->>Parser: 提取安装形式: "A0"
Parser->>Parser: 提取壳体形式: "532"
Parser->>Parser: 提取过程连接: "M203"
Parser->>Parser: 提取量程范围: "M16"
Parser->>Parser: 提取仪表特性: "Y3"
Note over Parser: 解析表盘型号
Parser->>Parser: 提取表盘型号: "BP-095"
Parser->>Parser: 提取表盘规格: "2312"
Parser->>Parser: 提取表盘连接: "M16"
Parser->>Parser: 提取表盘特性: "PA3"
Parser-->>BOMMgr: True (解析成功)
deactivate Parser
BOMMgr->>Extractor: New clsConditionExtractor
activate Extractor
BOMMgr->>Extractor: ExtractConditions(Parser)
Note over Extractor: 应用提取规则
Extractor->>Extractor: 规则1: gclj = 过程连接 = "M203"
Extractor->>Extractor: 规则2: jycz = 接液材质 = "M16"
Extractor->>Extractor: 规则3: lcfw = 量程范围 = "M16"
Extractor->>Extractor: 创建条件字典
Extractor-->>BOMMgr: Conditions Dictionary
deactivate Extractor
BOMMgr-->>Main: Conditions Dictionary
deactivate BOMMgr
%% ==================== 写入提取条件 ====================
Note over Main: 9. 写入提取条件行
Main->>PickListWS: Cells(row, 1).value = "提取条件"
Note over Main: 遍历条件字典拼接字符串
Main->>Conditions: 遍历 Keys
loop 每个条件键值对
Conditions-->>Main: key, value
Main->>Main: 拼接条件字符串到 condStr
end
Main->>PickListWS: Cells(row, 2).value = condStr
Main->>Main: row = row + 2
%% ==================== 写入表头 ====================
Note over Main: 10. 写入数据表头
Main->>PickListWS: Cells(row, 1).value = "类别"
Main->>PickListWS: Cells(row, 2).value = "代号"
Main->>PickListWS: Cells(row, 3).value = "名称"
Main->>PickListWS: Cells(row, 4).value = "数量"
Main->>PickListWS: Cells(row, 5).value = "选择条件"
Main->>PickListWS: Cells(row, 6).value = "匹配状态"
Main->>PickListWS: 设置表头字体为粗体 (Range A-F)
Main->>Main: row = row + 1
%% ==================== 获取根类别 ====================
Note over Main: 11. 获取根类别集合
Main->>BOMMgr: GetRootCategories()
BOMMgr-->>Main: RootCats Collection
%% ==================== 外层循环:遍历根类别 ====================
Note over Main: 12. 遍历每个根类别
loop 每个根类别 i = 1 To RootCats.Count
Main->>RootCats: Item(i)
RootCats-->>Main: Cat
Note over Main: 获取类别名称
Main->>Cat: .categoryName
Cat-->>Main: categoryName
Note over Main: 获取该类别符合条件的物料
Main->>BOMMgr: GetMaterialsByModel(modelStr, categoryName)
activate BOMMgr
BOMMgr->>Matcher: New clsConditionMatcher
activate Matcher
Note over BOMMgr: 从 dictCategories 获取类别
BOMMgr->>BOMMgr: dictCategories(categoryName)
BOMMgr-->>BOMMgr: Category 对象
Note over BOMMgr: 遍历类别下的所有物料
BOMMgr->>Cat: .materials (Collection)
loop 每个物料
Cat-->>BOMMgr: MaterialItem
Note over BOMMgr: 检查物料选择条件
BOMMgr->>MaterialItem: .Condition
alt 物料有选择条件
MaterialItem-->>BOMMgr: conditionStr
Note over BOMMgr,Matcher: 匹配条件
BOMMgr->>Matcher: Match(conditionStr, Conditions)
Matcher->>Matcher: 解析条件表达式<br/>AND, OR, !=, 括号)
Matcher->>Conditions: 获取变量值
Conditions-->>Matcher: 变量值
Matcher->>Matcher: 评估逻辑表达式
Matcher-->>BOMMgr: True/False (匹配结果)
alt 匹配成功
BOMMgr->>BOMMgr: 添加到结果集合
end
else 物料无条件
MaterialItem-->>BOMMgr: ""
BOMMgr->>BOMMgr: 直接添加到结果集合
end
end
BOMMgr-->>Main: Materials Collection
deactivate Matcher
deactivate BOMMgr
%% ==================== 内层循环:遍历物料写入 ====================
Note over Main: 写入该类别的所有物料
loop 每个物料 j = 1 To Materials.Count
Main->>Materials: Item(j)
Materials-->>Main: mat (clsMaterialItem)
Note over Main: 写入物料详细信息
Main->>PickListWS: Cells(row, 1).value = categoryName
Main->>PickListWS: Cells(row, 2).value = mat.code
Main->>PickListWS: Cells(row, 3).value = mat.Name
Main->>PickListWS: Cells(row, 4).value = mat.Quantity
Main->>mat: .Condition
mat-->>Main: condition
alt condition = ""
Main->>PickListWS: Cells(row, 5).value = "(无条件)"
else condition <> ""
Main->>PickListWS: Cells(row, 5).value = condition
end
Main->>PickListWS: Cells(row, 6).value = "✓"
Main->>Main: row = row + 1
end
end
%% ==================== 格式化输出 ====================
Note over Main: 13. 格式化工作表
Main->>PickListWS: Columns("A:F").AutoFit
%% ==================== 显示完成消息 ====================
Note over Main: 14. 显示完成消息框
Main->>Main: MsgBox("领料清单生成完成!\n请查看工作表: 型号领料清单")
Note over Main: 过程结束
```
## 流程阶段说明
### 阶段 1: 初始化与数据加载 (步骤 1-3)
- 创建 BOM 管理器实例
- 从 Excel 获取配置工作表
- 加载类别层级和物料数据,构建内存数据结构
### 阶段 2: 工作表准备 (步骤 4-7)
- 定义产品型号字符串
- 删除旧的输出工作表(带错误处理)
- 创建新的"型号领料清单"工作表
- 写入产品型号标题
### 阶段 3: 型号解析与条件提取 (步骤 8-9)
- 使用 ModelParser 解析复杂型号字符串
- 表头型号YTHN-100.A0.532.M203.M16.Y3
- 表盘型号BP-095.2312.M16.PA3
- 使用 ConditionExtractor 提取物料选择条件
- 将条件字典写入输出表
### 阶段 4: 数据表头 (步骤 10-11)
- 写入数据列标题:类别、代号、名称、数量、选择条件、匹配状态
- 获取根类别集合
### 阶段 5: 物料匹配与写入 (步骤 12)
- **外层循环**:遍历每个根类别
- **物料匹配**:对每个类别调用 GetMaterialsByModel
- 遍历类别下所有物料
- 使用 ConditionMatcher 评估每个物料的选择条件
- 筛选符合条件的物料
- **内层循环**:遍历匹配的物料集合
- 写入物料的详细信息(代号、名称、数量等)
- 显示选择条件和匹配状态
### 阶段 6: 格式化与完成 (步骤 13-14)
- 自动调整列宽
- 显示完成消息框
## 关键数据结构
### 输入数据
- **产品型号**: "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
- **配置工作表**: "领料配置"、"平台配置清单"
### 中间数据
- **Conditions Dictionary**: {gclj: "M203", jycz: "M16", lcfw: "M16"}
- **RootCategories Collection**: 所有顶层类别
- **Materials Collection**: 每个类别匹配的物料
### 输出数据
- **型号领料清单表**: 包含产品型号、提取条件、物料清单
## 性能考虑
- 使用 Dictionary 和 Collection 提高查找效率
- 嵌套循环结构:根类别 × 物料
- 大量 Excel 单元格写入操作(可考虑批量写入优化)
## 扩展建议
1. **批量写入优化**: 将数据先存入数组,一次性写入工作表
2. **进度显示**: 添加处理进度条
3. **错误处理**: 增强型号解析失败、数据缺失等异常处理
4. **参数化**: 将型号作为参数传入,增加灵活性
5. **日志记录**: 记录处理过程和匹配统计信息
---
## 3. 两个版本的对比
### 复杂度对比
| 特性 | 简略版 | 详细版 |
|------|--------|--------|
| **参与者数量** | 6个 | 11个 |
| **主要阶段** | 6个 | 14个步骤 |
| **交互步骤** | 约35步 | 约80步 |
| **循环展开** | 简化展示 | 完整展示内部逻辑 |
| **条件分支** | 隐藏 | 详细展示 (alt/opt/loop) |
| **内部方法调用** | 合并展示 | 逐个展开 |
### 适用场景
#### 简略版适用于:
- 📌 **初次了解**:快速掌握整体流程和架构
- 📌 **高层汇报**:向非技术人员展示系统工作原理
- 📌 **文档概览**:作为技术文档的目录或概述部分
- 📌 **快速参考**:复习主要步骤和组件关系
#### 详细版适用于:
- 🔍 **代码调试**:理解具体实现细节和数据流向
- 🔍 **代码审查**:检查逻辑错误和性能瓶颈
- 🔍 **新人培训**:深入讲解系统实现机制
- 🔍 **维护文档**:作为详细技术文档保存
### 组件对应关系
| 简略版组件 | 详细版组件 |
|------------|------------|
| Excel工作表 | Workbooks + ConfigWS + PlatformWS + PickListWS |
| 型号解析组件 | Parser + Extractor + Matcher |
| 数据集合 | RootCats + Cat + Materials + Conditions |
### 阶段对应关系
| 简略版阶段 | 详细版步骤 |
|------------|------------|
| 1. 初始化与加载数据 | 1-3: 初始化、获取工作表、加载数据 |
| 2. 创建输出工作表 | 4-7: 定义型号、删除旧表、创建新表、写入标题 |
| 3. 解析型号并提取条件 | 8-9: 解析型号、提取条件、写入条件行 |
| 4. 写入数据表头 | 10-11: 写入列标题、获取根类别 |
| 5. 遍历类别并匹配物料 | 12: 外层循环(类别)、内层循环(物料)、条件匹配 |
| 6. 格式化并完成 | 13-14: 格式化工作表、显示消息框 |
---
## 使用建议
1. **学习路径**:先看简略版建立整体概念,再看详细版理解实现细节
2. **文档组织**:简略版放在文档开头作为概述,详细版放在后面作为参考
3. **演示场景**简略版适合PPT演示详细版适合技术讲解
4. **版本维护**:代码变更时,优先更新详细版,然后同步更新简略版的主要流程

View File

@@ -1,530 +0,0 @@
# clsBOMManager.LoadData 方法流程分析 (v2.0 - 整合后版本)
## 方法概述
`LoadData``clsBOMManager` 类的核心方法,负责从 [平台配置清单] 工作表加载 BOM 数据并构建完整的层级数据结构。
**v2.0 变更说明**
- 移除了 [领料配置] 表的依赖
- 所有配置数据统一从 [平台配置清单] 读取
- 新增"类别选用条件"字段,支持动态判断类别是否需要
- 新增"66代码"字段
## 方法签名
```vba
Public Sub LoadData(wsPlatform As Worksheet)
```
### 参数说明
| 参数 | 类型 | 说明 |
|------|------|------|
| `wsPlatform` | Worksheet | [平台配置清单]工作表,包含完整的物料信息和类别配置 |
## 整体流程图
```mermaid
flowchart TD
Start([开始 LoadData]) --> Step1[第一步: 从平台配置清单加载所有物料和类别信息]
Step1 --> Step2[第二步: 建立类别层级关系]
Step2 --> End([结束])
style Start fill:#e1f5e1
style End fill:#ffe1e1
style Step1 fill:#e1f0ff
style Step2 fill:#fff4e1
```
## 详细流程分析
### 第一步:从平台配置清单加载所有物料和类别信息
```mermaid
flowchart TD
S1_Start([开始第一步]) --> S1_Init[初始化: 获取最后一行行号]
S1_Init --> S1_Loop{循环 i = 4 到 lastRow}
S1_Loop -->|i += 1| S1_ReadMat[读取物料基础信息<br/>C列:代号, D列:名称<br/>E列:数量, F列:选择条件]
S1_ReadMat --> S1_ReadCat[读取类别信息<br/>H列:类别, I列:上层类别<br/>J列:类别选用条件, K列:66代码]
S1_ReadCat --> S1_CreateMat[创建 clsMaterialItem 对象]
S1_CreateMat --> S1_SetMatProps[设置物料属性<br/>code, Name, Quantity, Condition<br/>Category, ParentCategory<br/>CategorySelectCondition, Code66]
S1_SetMatProps --> S1_SaveMat[保存到 dictAllMaterials]
S1_SaveMat --> S1_CheckCat{类别字段<br/>是否为空?}
S1_CheckCat -->|否, 有类别| S1_EnsureCat{类别对象<br/>是否存在?}
S1_CheckCat -->|是, 无类别| S1_Next
S1_EnsureCat -->|不存在| S1_CreateCat[创建新类别对象<br/>设置 categoryName, ParentCategoryName<br/>CategorySelectCondition]
S1_EnsureCat -->|已存在| S1_AddMat
S1_CreateCat --> S1_AddMat[将物料添加到类别]
S1_AddMat --> S1_Next
S1_Next --> S1_Loop
S1_Loop -->|i > lastRow| S1_End([第一步完成])
style S1_Start fill:#e1f5e1
style S1_End fill:#ffe1e1
style S1_CreateCat fill:#bbdefb
style S1_AddMat fill:#c8e6c9
style S1_CheckCat fill:#fff9c4
```
**数据来源**[平台配置清单] 工作表
| 列 | 字段 | 说明 |
|----|------|------|
| C | code | 物料代号 |
| D | Name | 物料名称 |
| E | Quantity | 物料数量 |
| F | Condition | 选择条件 |
| H | Category | 类别名称 |
| I | ParentCategoryName | 上层类别名称 |
| J | CategorySelectCondition | 类别选用条件 |
| K | Code66 | 66系统代码 |
**数据结构**
```
dictAllMaterials: Dictionary<String, clsMaterialItem>
├── "01011009557" → clsMaterialItem{
│ code: "01011009557"
│ Name: "径向低压接头部件"
│ Quantity: 1.0
│ Condition: "gclj=M20 AND jycz=1"
│ Category: "部件"
│ ParentCategory: ""
│ CategorySelectCondition: ""
│ Code66: "66021009557"
│ }
└── ...
dictCategories: Dictionary<String, clsCategory>
├── "部件" → clsCategory{
│ categoryName: "部件"
│ ParentCategoryName: ""
│ CategorySelectCondition: ""
│ materials: Collection<clsMaterialItem>
│ SubCategories: Collection<clsCategory>
│ }
└── ...
```
**关键代码**
```vba
lastRow = wsPlatform.Cells(wsPlatform.Rows.Count, "C").End(xlUp).row
For i = 4 To lastRow
' 1. 创建物料对象并读取基础信息
Set mat = New clsMaterialItem
mat.code = Trim(wsPlatform.Cells(i, "C").value & "")
mat.Name = Trim(wsPlatform.Cells(i, "D").value & "")
mat.Quantity = CDbl(wsPlatform.Cells(i, "E").value)
mat.Condition = Trim(wsPlatform.Cells(i, "F").value & "")
' 2. 读取类别相关字段
catName = Trim(wsPlatform.Cells(i, "H").value & "")
parentCatName = Trim(wsPlatform.Cells(i, "I").value & "")
catSelectCond = Trim(wsPlatform.Cells(i, "J").value & "")
code66 = Trim(wsPlatform.Cells(i, "K").value & "")
' 3. 保存到物料对象
mat.Category = catName
mat.ParentCategory = parentCatName
mat.CategorySelectCondition = catSelectCond
mat.Code66 = code66
' 4. 保存到字典(所有物料都保存)
If mat.code <> "" Then
Set dictAllMaterials(mat.code) = mat
End If
' 5. 如果有类别,创建或更新类别对象
If catName <> "" Then
If Not dictCategories.Exists(catName) Then
Set cat = New clsCategory
cat.categoryName = catName
cat.ParentCategoryName = parentCatName
cat.CategorySelectCondition = catSelectCond
Set dictCategories(catName) = cat
End If
dictCategories(catName).AddMaterial mat
End If
Next i
```
**与 v1.0 的区别**
| 特性 | v1.0 (旧版本) | v2.0 (新版本) |
|------|--------------|--------------|
| 数据源 | [平台配置清单] + [领料配置] | 仅 [平台配置清单] |
| 加载步骤 | 3步 (先物料,后类别,再层级) | 2步 (物料类别一起加载,再层级) |
| 参数 | wsConfig, wsPlatform | wsPlatform |
| 类别选用条件 | 不支持 | 支持 |
| 66代码 | 不支持 | 支持 |
---
### 第二步:建立类别层级关系
```mermaid
flowchart TD
S2_Start([开始第二步]) --> S2_Loop{遍历 dictCategories<br/>所有键}
S2_Loop -->|下一个键| S2_GetCat[获取类别对象 cat]
S2_GetCat --> S2_CheckParent{ParentCategoryName<br/>是否为空?}
S2_CheckParent -->|否, 有父类别| S2_ParentExists{父类别<br/>存在?}
S2_CheckParent -->|是, 无父类别| S2_AddRoot[添加到 rootCategories<br/>作为根类别]
S2_ParentExists -->|是| S2_AddSub[添加到父类别的<br/>SubCategories 集合]
S2_ParentExists -->|否| S2_Orphan[父类别不存在<br/>跳过建立关系]
S2_AddRoot --> S2_Next
S2_AddSub --> S2_Next
S2_Orphan --> S2_Next
S2_Next --> S2_Loop
S2_Loop -->|遍历完成| S2_End([第二步完成])
style S2_Start fill:#e1f5e1
style S2_End fill:#ffe1e1
style S2_AddRoot fill:#ffccbc
style S2_AddSub fill:#b2dfdb
```
**数据结构**
```
rootCategories: Collection<clsCategory>
├── "表壳" (根类别)
│ └── CategorySelectCondition: "" (总是需要)
├── "部件" (根类别)
│ └── CategorySelectCondition: "" (总是需要)
│ └── SubCategories:
│ ├── "接头" (子类别)
│ │ └── CategorySelectCondition: "gclj=M20" (条件性需要)
│ └── "弹性元件" (子类别)
│ └── CategorySelectCondition: "lcfw=M02" (条件性需要)
└── "机芯" (根类别)
└── CategorySelectCondition: "jycz=1" (条件性需要)
```
**关键代码**
```vba
For Each key In dictCategories.Keys
Set cat = dictCategories(key)
If cat.ParentCategoryName <> "" Then
If dictCategories.Exists(cat.ParentCategoryName) Then
Set parentCat = dictCategories(cat.ParentCategoryName)
parentCat.AddSubCategory cat
End If
Else
rootCategories.Add cat, cat.categoryName
End If
Next key
```
**说明**:此步骤与 v1.0 版本相同,未发生变化。
---
## 数据结构总结
### 类的私有成员变量
```mermaid
classDiagram
class clsBOMManager {
-dictCategories: Dictionary
-dictAllMaterials: Dictionary
-rootCategories: Collection
+LoadData(wsPlatform)
+GetRootCategories() Collection
+GetCategory(categoryName) clsCategory
+GetRequiredCategories(conditions) Collection
+GetValidMaterialsByModel(modelStr) Collection
}
class clsCategory {
+categoryName: String
+ParentCategoryName: String
+CategorySelectCondition: String
+materials: Collection
+SubCategories: Collection
+IsLeafCategory: Boolean
+AddMaterial(mat)
+AddSubCategory(cat)
+IsRequiredForModel(conditions) Boolean
+HasSubCategories() Boolean
}
class clsMaterialItem {
+code: String
+Name: String
+Quantity: Double
+Condition: String
+Category: String
+ParentCategory: String
+CategorySelectCondition: String
+Code66: String
}
clsBOMManager "1" --> "*" clsCategory : 管理
clsCategory "1" --> "*" clsMaterialItem : 包含
clsCategory "1" --> "*" clsCategory : 父子关系
```
### 三个核心数据容器
| 数据容器 | 类型 | 键/索引 | 值 | 用途 |
|---------|------|---------|-----|------|
| `dictAllMaterials` | Dictionary | 物料代号 (String) | clsMaterialItem | 快速查找任意物料的完整信息 |
| `dictCategories` | Dictionary | 类别名称 (String) | clsCategory | 快速查找任意类别 |
| `rootCategories` | Collection | 索引 (Long) | clsCategory | 遍历完整的类别树结构 |
---
## 流程时序图
```mermaid
sequenceDiagram
participant Caller as 调用者
participant LoadData as LoadData方法
participant WSPlatform as [平台配置清单]
participant DictMat as dictAllMaterials
participant DictCat as dictCategories
participant RootCats as rootCategories
Caller->>LoadData: LoadData(wsPlatform)
Note over LoadData, WSPlatform: 第一步: 加载物料和类别信息
LoadData->>WSPlatform: 读取第4-末行
WSPlatform-->>LoadData: 返回物料和类别数据
loop 每一行数据
LoadData->>DictMat: 创建/添加物料 (包含新字段)
alt 类别字段不为空
LoadData->>DictCat: 类别存在?
alt 类别不存在
LoadData->>DictCat: 创建新类别(含类别选用条件)
end
LoadData->>DictCat: 添加物料到类别
end
end
Note over LoadData, RootCats: 第二步: 构建层级树
loop 遍历所有类别
LoadData->>DictCat: 获取类别
alt 有父类别
LoadData->>DictCat: 添加到父类别的SubCategories
else 无父类别
LoadData->>RootCats: 添加为根类别
end
end
LoadData-->>Caller: 完成
```
---
## 重要业务规则
### 1. 物料筛选规则
- ✅ 类别字段不为空的物料 → **需要领料**,会被添加到类别中
- ❌ 类别字段为空的物料 → **不需要领料**,仅在 `dictAllMaterials` 中保存
### 2. 类别选用条件规则v2.0 新增)
- ✅ 类别选用条件为空 → 该类别**总是需要**(无条件限制)
- ⚡ 类别选用条件不为空 → 需要用型号条件去匹配
- 匹配成功 → 该类别需要
- 匹配失败 → 该类别不需要
### 3. 类别层级规则
- 根类别:`ParentCategoryName` 为空字符串 `""`
- 子类别:`ParentCategoryName` 指向父类别名称
- 层级深度:无限制(支持任意深度的树形结构)
### 4. 数据一致性
- 所有物料必须从 [平台配置清单] 读取
- 类别字段和类别选用条件字段同时存在
- 一个物料只能属于一个类别
---
## 使用示例
### 调用 LoadData
```vba
Dim bomMgr As New clsBOMManager
Dim wsPlatform As Worksheet
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' 加载数据 (v2.0 只需要一个参数)
Call bomMgr.LoadData(wsPlatform)
' 获取根类别
Dim rootCats As Collection
Set rootCats = bomMgr.GetRootCategories()
' 遍历所有根类别
Dim i As Long
For i = 1 To rootCats.Count
Debug.Print "根类别: " & rootCats(i).categoryName
Debug.Print "类别选用条件: " & rootCats(i).CategorySelectCondition
Next i
```
### 使用类别选用条件
```vba
' 解析型号并提取条件
Dim conditions As Object
Set conditions = bomMgr.ParseModelAndExtractConditions("YTHN-100.A0.532.M203.M16.Y3")
' 获取需要的类别
Dim requiredCats As Collection
Set requiredCats = bomMgr.GetRequiredCategories(conditions)
' 遍历需要的类别
Dim cat As clsCategory
For Each cat In requiredCats
Debug.Print "需要的类别: " & cat.categoryName
Next cat
```
---
## 执行后的数据结构示例
假设加载后的数据结构如下:
```
型号: YTHN-100.A0.532.M203.M16.Y3
提取的条件: lcfw=M16, gclj=M20, jycz=1, azxs=A0
dictAllMaterials:
{
"01011009557" → {
code: "01011009557",
Name: "径向低压接头部件",
Quantity: 1.0,
Condition: "gclj=M20 AND jycz=1 AND lcfw=M01",
Category: "部件",
ParentCategory: "",
CategorySelectCondition: "",
Code66: "66021009557"
}
}
dictCategories:
{
"部件" → {
categoryName: "部件",
ParentCategoryName: "",
CategorySelectCondition: "",
materials: [...],
SubCategories: ["接头", "弹性元件"]
},
"接头" → {
categoryName: "接头",
ParentCategoryName: "部件",
CategorySelectCondition: "gclj=M20", ← 类别选用条件
materials: [...],
SubCategories: []
},
"机芯" → {
categoryName: "机芯",
ParentCategoryName: "",
CategorySelectCondition: "jycz=1", ← 类别选用条件
materials: [...],
SubCategories: []
}
}
判断哪些类别需要:
- "部件" → CategorySelectCondition="" → 总是需要 ✓
- "接头" → CategorySelectCondition="gclj=M20" → 型号有gclj=M20 → 需要 ✓
- "机芯" → CategorySelectCondition="jycz=1" → 型号有jycz=1 → 需要 ✓
requiredCategories = ["部件", "接头", "机芯"]
```
---
## 错误处理
LoadData 方法本身不包含显式的错误处理(`On Error`),依赖以下机制:
1. **空值处理**:使用 `& ""` 确保字符串转换不会失败
2. **类型转换**:使用 `CDbl()` 时没有错误处理,假设数据格式正确
3. **跳过机制**:物料代号为空时,不保存到字典
4. **存在性检查**:使用 `Dictionary.Exists()` 避免键不存在错误
---
## 相关方法
LoadData 执行后,可以使用以下方法访问数据:
| 方法 | 说明 | v2.0变化 |
|------|------|---------|
| `GetRootCategories()` | 获取所有顶层类别 | 无变化 |
| `GetCategory(categoryName)` | 根据名称获取类别对象 | 无变化 |
| `GetRequiredCategories(conditions)` | ⭐ 获取需要的类别列表 | 新增 |
| `GetValidMaterialsByModel()` | 获取物料并检查完整性 | ⭐ 已重构,使用类别选用条件 |
| `GetMaterialsByModel()` | 根据型号获取物料 | 无变化 |
| `PrintCategoryTree()` | 打印类别树(调试用) | 无变化 |
---
## 迁移指南 (v1.0 → v2.0)
### 代码变更
**旧代码 (v1.0)**
```vba
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
Call bomMgr.LoadData(wsConfig, wsPlatform) ' 两个参数
```
**新代码 (v2.0)**
```vba
Dim wsPlatform As Worksheet
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
Call bomMgr.LoadData(wsPlatform) ' 一个参数
```
### 数据变更
**旧表结构**
- [领料配置]: A列=代号, C列=类别, D列=上层类别
- [平台配置清单]: C列=代号, D列=名称, E列=数量, F列=选择条件
**新表结构**
- [平台配置清单]: C列=代号, D列=名称, E列=数量, F列=选择条件
H列=类别, I列=上层类别, J列=类别选用条件, K列=66代码
### 逻辑变更
**完整性检查逻辑**
- **v1.0**: 所有根类别都必须匹配到物料
- **v2.0**: 只检查需要的类别(通过类别选用条件判断)
---
## 总结
`LoadData` 方法v2.0)通过两个阶段构建完整的 BOM 数据结构:
1. **数据加载**:从 [平台配置清单] 一次性加载所有物料和类别信息包括新增的类别选用条件和66代码
2. **结构构建**:构建类别的树形层级关系
v2.0 的主要改进:
- ✅ 简化数据源:从一个表读取所有数据
- ✅ 支持动态类别判断:通过类别选用条件
- ✅ 更精确的完整性检查:只检查需要的类别
- ✅ 支持66系统新增66代码字段
最终形成的数据结构支持:
- 快速按代号查找物料
- 快速按类别查找物料
- 遍历完整的类别树
- 根据型号动态确定需要的类别
- 支持父类别/子类别的领料逻辑

View File

@@ -1,439 +0,0 @@
# clsBOMManager.LoadData 方法流程分析
## 方法概述
`LoadData``clsBOMManager` 类的核心方法,负责从 Excel 工作表加载 BOM 数据并构建完整的层级数据结构。
## 方法签名
```vba
Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet)
```
### 参数说明
| 参数 | 类型 | 说明 |
|------|------|------|
| `wsConfig` | Worksheet | [领料配置]工作表,包含类别层级和需要领料的物料 |
| `wsPlatform` | Worksheet | [平台配置清单]工作表,包含完整的物料信息 |
## 整体流程图
```mermaid
flowchart TD
Start([开始 LoadData]) --> Step1[第一步: 加载物料基础信息]
Step1 --> Step2[第二步: 加载类别信息并关联物料]
Step2 --> Step3[第三步: 建立类别层级关系]
Step3 --> End([结束])
style Start fill:#e1f5e1
style End fill:#ffe1e1
style Step1 fill:#e1f0ff
style Step2 fill:#fff4e1
style Step3 fill:#f0e1ff
```
## 详细流程分析
### 第一步:从平台配置清单加载所有物料的基础信息
```mermaid
flowchart TD
S1_Start([开始第一步]) --> S1_Init[初始化: 获取最后一行行号]
S1_Init --> S1_Loop{循环 i = 4 到 lastRow}
S1_Loop -->|i += 1| S1_Read[读取第 i 行数据]
S1_Read --> S1_Create[创建 clsMaterialItem 对象]
S1_Create --> S1_Assign[赋值: code, Name, Quantity, Condition]
S1_Assign --> S1_Check{code 是否非空?}
S1_Check -->|是| S1_Add[添加到 dictAllMaterials<br/>键: code, 值: mat]
S1_Check -->|否| S1_Next[跳过]
S1_Add --> S1_Next
S1_Next --> S1_Loop
S1_Loop -->|i > lastRow| S1_End([第一步完成])
style S1_Start fill:#e1f5e1
style S1_End fill:#ffe1e1
style S1_Add fill:#c8e6c9
style S1_Check fill:#fff9c4
```
**数据来源**[平台配置清单] 工作表
| 列 | 字段 | 说明 |
|----|------|------|
| C | code | 物料代号 |
| D | Name | 物料名称 |
| E | Quantity | 物料数量 |
| F | Condition | 选择条件 |
**数据结构**
```
dictAllMaterials: Dictionary<String, clsMaterialItem>
├── "01011019001" → clsMaterialItem{code, Name, Quantity, Condition}
├── "01081013833" → clsMaterialItem{...}
└── ...
```
**关键代码**
```vba
lastRow = wsPlatform.Cells(wsPlatform.Rows.Count, "C").End(xlUp).row
For i = 4 To lastRow
Set mat = New clsMaterialItem
mat.code = Trim(wsPlatform.Cells(i, "C").value & "")
mat.Name = Trim(wsPlatform.Cells(i, "D").value & "")
mat.Quantity = CDbl(wsPlatform.Cells(i, "E").value)
mat.Condition = Trim(wsPlatform.Cells(i, "F").value & "")
If mat.code <> "" Then
Set dictAllMaterials(mat.code) = mat
End If
Next i
```
---
### 第二步:从领料配置加载类别信息并关联物料
```mermaid
flowchart TD
S2_Start([开始第二步]) --> S2_Init[初始化: 获取最后一行行号]
S2_Init --> S2_Loop{循环 i = 2 到 lastRow}
S2_Loop -->|i += 1| S2_Read[读取第 i 行数据<br/>code, catName, parentCatName]
S2_Read --> S2_CheckCode{code 是否为空?}
S2_CheckCode -->|是| S2_NextRow[跳到下一行]
S2_CheckCode -->|否| S2_CheckCat{类别是否已存在?}
S2_CheckCat -->|否| S2_CreateCat[创建新的 clsCategory 对象<br/>设置 categoryName 和 ParentCategoryName<br/>添加到 dictCategories]
S2_CheckCat -->|是| S2_Exists[类别已存在]
S2_CreateCat --> S2_CheckMat{物料在<br/>dictAllMaterials?}
S2_Exists --> S2_CheckMat
S2_CheckMat -->|是| S2_UpdateMat[更新物料信息:<br/>mat.Category = catName<br/>mat.ParentCategory = parentCatName<br/>添加到类别 materials 集合]
S2_CheckMat -->|否| S2_SkipMat[物料不在库中,跳过]
S2_UpdateMat --> S2_NextRow
S2_SkipMat --> S2_NextRow
S2_NextRow --> S2_Loop
S2_Loop -->|i > lastRow| S2_End([第二步完成])
style S2_Start fill:#e1f5e1
style S2_End fill:#ffe1e1
style S2_CreateCat fill:#bbdefb
style S2_UpdateMat fill:#c8e6c9
style S2_CheckCat fill:#fff9c4
style S2_CheckMat fill:#fff9c4
```
**数据来源**[领料配置] 工作表
| 列 | 字段 | 说明 |
|----|------|------|
| A | code | 物料代号 |
| C | catName | 类别名称 |
| D | parentCatName | 上层类别名称 |
**数据结构**
```
dictCategories: Dictionary<String, clsCategory>
├── "表壳" → clsCategory{categoryName, materials[], SubCategories[]}
├── "部件" → clsCategory{...}
└── ...
```
**关键代码**
```vba
lastRow = wsConfig.Cells(wsConfig.Rows.Count, "A").End(xlUp).row
For i = 2 To lastRow
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
If dictAllMaterials.Exists(code) Then
Set mat = dictAllMaterials(code)
mat.Category = catName
mat.ParentCategory = parentCatName
dictCategories(catName).AddMaterial mat
End If
Next i
```
**业务逻辑说明**
- 只有出现在 [领料配置] 中的物料才会被添加到类别中
- 未在 [领料配置] 中的物料表示不需要领料
---
### 第三步:建立类别层级关系
```mermaid
flowchart TD
S3_Start([开始第三步]) --> S3_Loop{遍历 dictCategories<br/>所有键}
S3_Loop -->|下一个键| S3_GetCat[获取类别对象 cat]
S3_GetCat --> S3_CheckParent{ParentCategoryName<br/>是否为空?}
S3_CheckParent -->|否, 有父类别| S3_ParentExists{父类别<br/>存在?}
S3_CheckParent -->|是, 无父类别| S3_AddRoot[添加到 rootCategories<br/>作为根类别]
S3_ParentExists -->|是| S3_AddSub[添加到父类别的<br/>SubCategories 集合]
S3_ParentExists -->|否| S3_Orphan[父类别不存在<br/>跳过建立关系]
S3_AddRoot --> S3_Next
S3_AddSub --> S3_Next
S3_Orphan --> S3_Next
S3_Next --> S3_Loop
S3_Loop -->|遍历完成| S3_End([第三步完成])
style S3_Start fill:#e1f5e1
style S3_End fill:#ffe1e1
style S3_AddRoot fill:#ffccbc
style S3_AddSub fill:#b2dfdb
```
**数据结构**
```
rootCategories: Collection<clsCategory>
├── "表壳" (根类别)
├── "部件" (根类别)
│ └── SubCategories:
│ ├── "接头" (子类别)
│ └── "弹性元件" (子类别)
└── "机芯" (根类别)
```
**关键代码**
```vba
For Each key In dictCategories.Keys
Set cat = dictCategories(key)
If cat.ParentCategoryName <> "" Then
If dictCategories.Exists(cat.ParentCategoryName) Then
Set parentCat = dictCategories(cat.ParentCategoryName)
parentCat.AddSubCategory cat
End If
Else
rootCategories.Add cat, cat.categoryName
End If
Next key
```
**层级关系构建逻辑**
1. 遍历所有类别
2. 如果类别有父类别 → 建立父子关系(调用父类别的 `AddSubCategory` 方法)
3. 如果类别无父类别 → 作为根类别,添加到 `rootCategories`
---
## 数据结构总结
### 类的私有成员变量
```mermaid
classDiagram
class clsBOMManager {
-dictCategories: Dictionary
-dictAllMaterials: Dictionary
-rootCategories: Collection
+LoadData(wsConfig, wsPlatform)
+GetRootCategories() Collection
+GetCategory(categoryName) clsCategory
}
class clsCategory {
+categoryName: String
+ParentCategoryName: String
+materials: Collection
+SubCategories: Collection
+AddMaterial(mat)
+AddSubCategory(cat)
+HasSubCategories: Boolean
}
class clsMaterialItem {
+code: String
+Name: String
+Quantity: Double
+Condition: String
+Category: String
+ParentCategory: String
}
clsBOMManager "1" --> "*" clsCategory : 管理
clsCategory "1" --> "*" clsMaterialItem : 包含
clsCategory "1" --> "*" clsCategory : 父子关系
```
### 三个核心数据容器
| 数据容器 | 类型 | 键/索引 | 值 | 用途 |
|---------|------|---------|-----|------|
| `dictAllMaterials` | Dictionary | 物料代号 (String) | clsMaterialItem | 快速查找任意物料的完整信息 |
| `dictCategories` | Dictionary | 类别名称 (String) | clsCategory | 快速查找任意类别 |
| `rootCategories` | Collection | 索引 (Long) | clsCategory | 遍历完整的类别树结构 |
---
## 流程时序图
```mermaid
sequenceDiagram
participant Caller as 调用者
participant LoadData as LoadData方法
participant WSPlatform as [平台配置清单]
participant WSConfig as [领料配置]
participant DictMat as dictAllMaterials
participant DictCat as dictCategories
participant RootCats as rootCategories
Caller->>LoadData: LoadData(wsConfig, wsPlatform)
Note over LoadData, WSPlatform: 第一步: 加载物料库
LoadData->>WSPlatform: 读取第4-末行, C-F列
WSPlatform-->>LoadData: 返回物料数据
loop 每一行物料
LoadData->>DictMat: 添加物料 (代号→对象)
end
Note over LoadData, WSConfig: 第二步: 建立类别与物料关联
LoadData->>WSConfig: 读取第2-末行, A/C/D列
WSConfig-->>LoadData: 返回类别配置
loop 每一行配置
LoadData->>DictCat: 类别存在?
alt 类别不存在
LoadData->>DictCat: 创建新类别
end
LoadData->>DictMat: 查找物料信息
alt 物料存在
LoadData->>DictCat: 添加物料到类别
end
end
Note over LoadData, RootCats: 第三步: 构建层级树
loop 遍历所有类别
LoadData->>DictCat: 获取类别
alt 有父类别
LoadData->>DictCat: 添加到父类别的SubCategories
else 无父类别
LoadData->>RootCats: 添加为根类别
end
end
LoadData-->>Caller: 完成
```
---
## 重要业务规则
### 1. 物料筛选规则
- ✅ 在 [领料配置] 中的物料 → **需要领料**,会被添加到类别中
- ❌ 不在 [领料配置] 中的物料 → **不需要领料**,仅在 `dictAllMaterials`
### 2. 类别层级规则
- 根类别:`ParentCategoryName` 为空字符串 `""`
- 子类别:`ParentCategoryName` 指向父类别名称
- 层级深度:无限制(支持任意深度的树形结构)
### 3. 数据一致性
- 所有物料必须先在 [平台配置清单] 中定义
- [领料配置] 中的物料代号必须在 `dictAllMaterials` 中存在
- 如果不存在,该物料会被跳过(不会报错)
---
## 使用示例
### 调用 LoadData
```vba
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet
Dim wsPlatform As Worksheet
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
' 加载数据
Call bomMgr.LoadData(wsConfig, wsPlatform)
' 获取根类别
Dim rootCats As Collection
Set rootCats = bomMgr.GetRootCategories()
' 遍历所有根类别
Dim i As Long
For i = 1 To rootCats.Count
Debug.Print "根类别: " & rootCats(i).categoryName
Next i
```
---
## 执行后的数据结构示例
假设加载后的数据结构如下:
```
dictAllMaterials:
{
"01091004312" → {code: "01091004312", Name: "表壳(本色)", Quantity: 1, Condition: ""},
"01011019001" → {code: "01011019001", Name: "低压接头部件", Quantity: 1, Condition: "lcfw=M02"},
"01081013833" → {code: "01081013833", Name: "径向低压接头", Quantity: 1, Condition: "gclj=Z12"}
}
dictCategories:
{
"表壳" → {categoryName: "表壳", ParentCategoryName: "", materials: [01091004312], SubCategories: []},
"部件" → {categoryName: "部件", ParentCategoryName: "", materials: [01011019001], SubCategories: ["接头", "弹性元件"]},
"接头" → {categoryName: "接头", ParentCategoryName: "部件", materials: [01081013833], SubCategories: []}
}
rootCategories:
[
"表壳" (clsCategory),
"部件" (clsCategory),
"机芯" (clsCategory)
]
```
---
## 错误处理
LoadData 方法本身不包含显式的错误处理(`On Error`),依赖以下机制:
1. **空值处理**:使用 `& ""` 确保字符串转换不会失败
2. **类型转换**:使用 `CDbl()` 时没有错误处理,假设数据格式正确
3. **跳过机制**:物料代号为空时,使用 `GoTo NextRow` 跳过
4. **存在性检查**:使用 `Dictionary.Exists()` 避免键不存在错误
---
## 相关方法
LoadData 执行后,可以使用以下方法访问数据:
| 方法 | 说明 |
|------|------|
| `GetRootCategories()` | 获取所有顶层类别 |
| `GetCategory(categoryName)` | 根据名称获取类别对象 |
| `GetMaterialsByModel()` | 根据型号获取物料 |
| `GetValidMaterialsByModel()` | 获取物料并检查完整性 |
| `PrintCategoryTree()` | 打印类别树(调试用) |
---
## 总结
`LoadData` 方法通过三个阶段构建完整的 BOM 数据结构:
1. **数据收集**:从 [平台配置清单] 收集所有物料基础信息
2. **数据关联**:从 [领料配置] 建立类别与物料的关联
3. **结构构建**:构建类别的树形层级关系
最终形成的数据结构支持:
- 快速按代号查找物料
- 快速按类别查找物料
- 遍历完整的类别树
- 支持父类别/子类别的领料逻辑

View File

@@ -1,545 +0,0 @@
# clsBOMManager 类使用说明
## 概述
`clsBOMManager` 是 AutoBOM 系统的核心控制器类,负责管理整个 BOM物料清单数据结构。它从 Excel 工作表加载数据,构建层级化的类别树,并提供物料查询和领料逻辑处理功能。
---
## 类初始化
```vba
Dim bomMgr As New clsBOMManager
```
创建实例时,会自动初始化以下内部数据结构:
- `dictCategories` - 类别字典(类别名称 → clsCategory 对象)
- `dictAllMaterials` - 物料字典(物料代号 → clsMaterialItem 对象)
- `rootCategories` - 根类别集合(顶层类别)
---
## 公开方法
### 1. LoadData 方法
**功能**:从 Excel 工作表加载 BOM 数据并构建数据结构
**语法**
```vba
Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet)
```
**参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| wsConfig | Worksheet | 领料配置工作表,定义类别层级和需要领料的物料 |
| wsPlatform | Worksheet | 平台配置清单工作表,包含完整的物料信息 |
**数据要求**
**领料配置** 工作表格式:
- **第 1 行**:标题行
- **第 2 行起**:数据行
- A 列:物料代号
- C 列:类别名称
- D 列:上层类别名称
**平台配置清单** 工作表格式:
- **第 1-3 行**:标题行
- **第 4 行起**:数据行
- C 列:物料代号
- D 列:物料名称
- E 列:物料数量
- F 列:选择条件
**处理逻辑**
1. 从"平台配置清单"加载所有物料的基础信息到 `dictAllMaterials`
2. 从"领料配置"加载类别信息,筛选需要领料的物料
3. 建立类别的父子关系,构建层级树
**重要说明**
- 只有出现在"领料配置"中的物料才会被添加到类别中
- 未在"领料配置"中的物料表示不需要领料,但仍存在于物料字典中
**使用示例**
```vba
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
```
---
### 2. GetRootCategories 方法
**功能**:获取所有顶层类别的集合
**语法**
```vba
Public Function GetRootCategories() As Collection
```
**返回值**
- `Collection` 对象,包含所有无父类别的 `clsCategory` 对象
**用途**
- 遍历整个 BOM 结构时的入口点
- 生成领料清单时遍历所有根类别
**使用示例**
```vba
Dim rootCats As Collection
Dim cat As clsCategory
Dim i As Long
Set rootCats = bomMgr.GetRootCategories()
For i = 1 To rootCats.Count
Set cat = rootCats(i)
Debug.Print "根类别: " & cat.categoryName
Debug.Print " 物料数: " & cat.Materials.Count
Debug.Print " 子类别数: " & cat.SubCategories.Count
Next i
```
---
### 3. GetCategory 方法
**功能**:根据类别名称获取类别对象
**语法**
```vba
Public Function GetCategory(categoryName As String) As clsCategory
```
**参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| categoryName | String | 要查询的类别名称 |
**返回值**
- 找到时:返回 `clsCategory` 对象
- 未找到时:返回 `Nothing`
**用途**:快速查找特定类别及其下的物料
**使用示例**
```vba
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.IsLeafCategory
End If
```
---
### 4. GetMaterialsForPicking 方法
**功能**:获取某类别下需要领料的物料清单(支持层级逻辑)
**语法**
```vba
Public Function GetMaterialsForPicking(categoryName As String, _
Optional useParent As Boolean = True) As Collection
```
**参数**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| categoryName | String | - | 类别名称 |
| useParent | Boolean | True | 领料方式True=父类别物料False=子类别物料 |
**返回值**
- `Collection` 对象,包含 `clsMaterialItem` 对象
**业务逻辑说明**
#### 父类别模式 (useParent=True)
- 这是**默认的领料方式**
- 直接返回当前类别下的所有物料
- 即使该类别有子类别,也仍然返回父类别物料
- 用于领取组装好的部件
**示例**
```vba
' 获取"部件"类别的父类别物料
Set materials = bomMgr.GetMaterialsForPicking("部件", True)
' 返回:低压接头部件、高压接头部件等组装好的部件
```
#### 子类别模式 (useParent=False)
- 这是**库存不足时的替代方案**
- 如果当前类别有子类别,递归获取所有子类别的物料
- 如果当前类别是叶子类别(无子类别),返回本类别物料
- 用于父类别库存不足,需要领取零件自行组装的情况
**示例**
```vba
' 获取"部件"类别的子类别展开物料
Set materials = bomMgr.GetMaterialsForPicking("部件", False)
' 返回:径向低压接头、弹簧管、螺旋管等零件
```
**层级结构示例**
```
部件 (父类别)
├── 低压接头部件 (物料)
├── 高压接头部件 (物料)
└── 接头 (子类别)
├── 径向低压接头 (物料)
├── 轴向高压接头 (物料)
└── 弹性元件 (孙类别)
└── 弹簧管 (物料)
```
**使用示例**
```vba
Dim materials As Collection
Dim mat As clsMaterialItem
Dim i As Long
' 默认使用父类别物料
Set materials = bomMgr.GetMaterialsForPicking("部件", True)
Debug.Print "父类别物料数: " & materials.Count
For i = 1 To materials.Count
Set mat = materials(i)
Debug.Print mat.code & " - " & mat.Name & _
" | 数量:" & mat.Quantity & _
" | 条件:" & mat.Condition
Next i
' 库存不足时,使用子类别物料
Set materials = bomMgr.GetMaterialsForPicking("部件", False)
Debug.Print "子类别展开物料数: " & materials.Count
```
---
### 5. PrintCategoryTree 方法
**功能**:将类别树结构打印到工作表(用于调试和查看)
**语法**
```vba
Public Sub PrintCategoryTree(ws As Worksheet)
```
**参数**
| 参数 | 类型 | 说明 |
|------|------|------|
| ws | Worksheet | 输出的目标工作表对象 |
**输出格式**
- 第 1 列:类别名称(带缩进显示层级)
- 第 2 列:物料数量信息
- 第 3 列:选择条件
**缩进规则**:每层级 2 个空格
**输出示例**
```
表壳 (物料数:1)
- 01091004312 表壳(本色) 数量:1 条件:
部件 (物料数:20)
- 01011019001 低压接头部件 数量:1 条件:lcfw=M02...
接头 (物料数:18)
- 01081013833 径向低压接头 数量:1 条件:gclj=Z12...
```
**使用示例**
```vba
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
```
---
## 典型使用场景
### 场景 1生成领料清单
```vba
Sub GeneratePickingList()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet, wsPlatform As Worksheet
Dim wsPickList As Worksheet
Dim rootCats As Collection
Dim cat As clsCategory
Dim materials As Collection
Dim mat As clsMaterialItem
Dim row As Long
Dim i As Long, j As Long
' 1. 加载数据
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 2. 创建输出工作表
Application.DisplayAlerts = False
On Error Resume Next
ThisWorkbook.Worksheets("领料清单").Delete
On Error GoTo 0
Application.DisplayAlerts = True
Set wsPickList = ThisWorkbook.Worksheets.Add
wsPickList.Name = "领料清单"
' 3. 写入表头
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
' 4. 遍历所有根类别并生成清单
Set rootCats = bomMgr.GetRootCategories
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
' 5. 格式化表格
wsPickList.Range("A1:F1").Font.Bold = True
wsPickList.Columns("A:F").AutoFit
MsgBox "领料清单生成完成!", vbInformation
End Sub
```
### 场景 2查询特定类别信息
```vba
Sub QueryCategoryInfo()
Dim bomMgr As New clsBOMManager
Dim wsConfig As Worksheet, wsPlatform As Worksheet
Dim cat As clsCategory
Dim mat As clsMaterialItem
Dim subCat As clsCategory
Dim i As Long
' 加载数据
Set wsConfig = ThisWorkbook.Worksheets("领料配置")
Set wsPlatform = ThisWorkbook.Worksheets("平台配置清单")
bomMgr.LoadData wsConfig, wsPlatform
' 查询特定类别
Set cat = bomMgr.GetCategory("部件")
If Not cat Is Nothing Then
Debug.Print "========== 类别信息 =========="
Debug.Print "类别名称: " & cat.categoryName
Debug.Print "父类别: " & cat.ParentCategoryName
Debug.Print "物料数量: " & cat.Materials.Count
Debug.Print "子类别数量: " & cat.SubCategories.Count
Debug.Print "是否叶子类别: " & cat.IsLeafCategory
Debug.Print ""
' 列出所有物料
Debug.Print "---------- 物料列表 ----------"
For i = 1 To cat.Materials.Count
Set mat = cat.Materials(i)
Debug.Print " " & mat.code & ": " & mat.Name & _
" (数量:" & mat.Quantity & _
", 条件:" & mat.Condition & ")"
Next i
Debug.Print ""
' 列出所有子类别
Debug.Print "---------- 子类别列表 ----------"
For i = 1 To cat.SubCategories.Count
Set subCat = cat.SubCategories(i)
Debug.Print " " & subCat.categoryName & _
" (物料数:" & subCat.Materials.Count & ")"
Next i
Else
Debug.Print "类别不存在"
End If
End Sub
```
### 场景 3根据库存情况选择领料方式
```vba
Sub SmartPicking()
Dim bomMgr As New clsBOMManager
Dim materials As Collection
Dim mat As clsMaterialItem
Dim categoryName As String
Dim i As Long
' 加载数据
bomMgr.LoadData _
ThisWorkbook.Worksheets("领料配置"), _
ThisWorkbook.Worksheets("平台配置清单")
categoryName = "部件"
' 尝试获取父类别物料
Set materials = bomMgr.GetMaterialsForPicking(categoryName, True)
' 检查库存是否充足(这里需要连接实际的库存系统)
Dim isStockSufficient As Boolean
isStockSufficient = CheckStock(materials) ' 假设的库存检查函数
If isStockSufficient Then
Debug.Print "使用父类别领料方式"
Else
Debug.Print "父类别库存不足,改用子类别领料方式"
Set materials = bomMgr.GetMaterialsForPicking(categoryName, False)
End If
' 输出物料清单
For i = 1 To materials.Count
Set mat = materials(i)
Debug.Print mat.code & " " & mat.Name
Next i
End Sub
```
---
## 辅助类说明
### clsCategory 类
表示物料类别及其层级关系。
**公共属性**
| 属性 | 类型 | 说明 |
|------|------|------|
| categoryName | String | 类别名称 |
| ParentCategoryName | String | 父类别名称 |
| Materials | Collection | 该类别下的物料集合 |
| SubCategories | Collection | 子类别集合 |
| IsLeafCategory | Boolean | 是否为叶子类别(无子类别) |
**公共方法**
- `AddMaterial(mat As clsMaterialItem)` - 添加物料
- `AddSubCategory(cat As clsCategory)` - 添加子类别
- `GetMaterial(code As String) As clsMaterialItem` - 获取指定代号的物料
- `HasSubCategories() As Boolean` - 检查是否有子类别
### clsMaterialItem 类
表示单个物料项。
**公共属性**
| 属性 | 类型 | 说明 |
|------|------|------|
| code | String | 物料代号 |
| Name | String | 物料名称 |
| Quantity | Double | 物料数量 |
| Condition | String | 选择条件 |
| Category | String | 所属类别 |
| ParentCategory | String | 上层类别 |
---
## 注意事项
1. **数据加载顺序**:必须先调用 `LoadData()` 方法,才能使用其他查询方法
2. **工作表名称**:确保工作表名称与代码中的名称一致:
- "领料配置"
- "平台配置清单"
3. **空值处理**:使用 `GetCategory()` 方法时,务必检查返回值是否为 `Nothing`
4. **领料方式选择**
- 正常情况使用 `useParent=True`(领取组装好的部件)
- 仅在库存不足时使用 `useParent=False`(领取零件自行组装)
5. **数据一致性**:修改 Excel 数据后,需要重新调用 `LoadData()` 重新加载数据
6. **内存管理**使用完毕后VBA 会自动清理对象,无需手动释放
---
## 常见问题
### Q1: 如何判断某个类别是否存在?
```vba
Dim cat As clsCategory
Set cat = bomMgr.GetCategory("类别名")
If Not cat Is Nothing Then
' 类别存在
Else
' 类别不存在
End If
```
### Q2: 如何获取所有类别的名称?
```vba
Sub ListAllCategories()
' 需要递归遍历所有类别
' 可以使用 PrintCategoryTree 方法输出到工作表查看
End Sub
```
### Q3: 父类别和子类别物料有什么区别?
- **父类别物料**:组装好的成品部件,如"低压接头部件"
- **子类别物料**:组成部件的零件,如"径向低压接头"、"弹簧管"等
### Q4: 如何处理物料选择条件?
```vba
Dim mat As clsMaterialItem
' ...
If mat.Condition <> "" Then
' 根据条件选择合适的物料
' 条件格式如lcfw=M02;gclj=Z12
' 需要根据产品配置解析这些条件
End If
```
---
## 版本信息
- **类名称**clsBOMManager
- **版本**1.0
- **最后更新**2025
- **依赖类**clsCategory, clsMaterialItem

File diff suppressed because it is too large Load Diff

View File

@@ -1,428 +0,0 @@
# 型号解析与物料匹配系统 - 使用说明
## 📋 目录
1. [系统概述](#系统概述)
2. [安装部署](#安装部署)
3. [快速开始](#快速开始)
4. [详细使用](#详细使用)
5. [API参考](#api参考)
6. [扩展指南](#扩展指南)
7. [常见问题](#常见问题)
---
## 系统概述
### 功能简介
本系统实现了从产品型号中自动提取物料选择条件并根据这些条件从BOM库中筛选出符合要求的物料清单。
### 核心功能
-**型号解析**: 自动解析产品型号的各个组成部分
-**条件提取**: 按规则提取物料选择条件(过程连接、接液材质、量程范围等)
-**条件匹配**: 支持复杂的逻辑表达式(AND/OR/NOT/括号)
-**物料筛选**: 自动筛选符合条件的物料
-**批量处理**: 支持批量处理多个型号
-**可扩展性**: 便于添加新的提取条件和匹配规则
### 示例
**输入型号**: `YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3`
**提取条件**:
- `gclj` (过程连接) = `M20`
- `jycz` (接液材质) = `3`
- `lcfw` (量程范围) = `M16`
**匹配结果**: `01011019018 - 高压接头部件`
---
## 安装部署
### 1. 导入类模块
将以下4个类模块文件导入到VBA项目中:
1. **clsModelParser.cls** - 型号解析器
2. **clsConditionExtractor.cls** - 条件提取器
3. **clsConditionMatcher.cls** - 条件匹配器
4. **clsBOMManager.cls** - BOM管理器(扩展版本)
### 2. 导入标准模块
将以下2个标准模块导入到VBA项目中:
1. **modModelParserTest.bas** - 测试模块
2. **modModelParserExamples.bas** - 应用示例模块
### 3. 验证安装
运行测试过程验证安装是否成功:
```vba
' 在立即窗口或通过运行按钮执行
RunAllTests
```
如果所有测试通过,说明安装成功!
---
## 快速开始
### 最简单的使用方式
```vba
Sub QuickStart()
' 1. 创建BOM管理器并加载数据
Dim bomMgr As New clsBOMManager
bomMgr.LoadData ThisWorkbook.Worksheets("领料配置"), _
ThisWorkbook.Worksheets("平台配置清单")
' 2. 根据型号获取物料
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M16.Y3"
Dim materials As Collection
Set materials = bomMgr.GetMaterialsByModel(modelStr, "部件")
' 3. 查看结果
Dim mat As clsMaterialItem
For Each mat In materials
Debug.Print mat.Code & " - " & mat.Name
Next mat
End Sub
```
### 生成领料清单
运行以下示例生成完整的领料清单:
```vba
Example1_GeneratePickingListByModel
```
这将创建一个新工作表,包含完整的领料清单。
---
## 详细使用
### 1. 型号解析
#### 基础解析
```vba
Dim parser As New clsModelParser
Dim modelStr As String
modelStr = "YTHN-100.A0.532.M203.M16.Y3|BP-095.2312.M16.PA3"
If parser.ParseModel(modelStr) Then
' 解析成功
Debug.Print "型号: " & parser.ModelType ' YTHN
Debug.Print "外径: " & parser.Diameter ' 100
Debug.Print "过程连接: " & parser.ConnectionCode ' M203
Debug.Print "量程: " & parser.RangeCode ' M16
Else
' 解析失败
Debug.Print "错误: " & parser.ErrorMessage
End If
```
#### 获取提取的代码
```vba
Debug.Print "螺纹代码: " & parser.GetThreadCode() ' M20
Debug.Print "材质代码: " & parser.GetMaterialCode() ' 3
Debug.Print "量程代码: " & parser.GetRangeCode() ' M16
```
### 2. 条件提取
```vba
Dim parser As New clsModelParser
parser.ParseModel "YTHN-100.A0.532.M203.M16.Y3"
Dim extractor As New clsConditionExtractor
Dim conditions As Object
Set conditions = extractor.ExtractConditions(parser)
' 获取单个条件
Debug.Print "过程连接: " & extractor.GetConditionValue("gclj") ' M20
Debug.Print "接液材质: " & extractor.GetConditionValue("jycz") ' 3
Debug.Print "量程范围: " & extractor.GetConditionValue("lcfw") ' M16
' 遍历所有条件
Dim key As Variant
For Each key In conditions.Keys
Debug.Print key & " = " & conditions(key)
Next key
```
### 3. 条件匹配
#### 简单条件
```vba
Dim matcher As New clsConditionMatcher
Dim conditions As Object
Set conditions = CreateObject("Scripting.Dictionary")
conditions("gclj") = "M20"
conditions("lcfw") = "M16"
' 等于
Debug.Print matcher.IsMatch("lcfw=M16", conditions) ' True
' 不等于
Debug.Print matcher.IsMatch("gclj!=M10", conditions) ' True
```
#### 复合条件
```vba
' AND 运算
Debug.Print matcher.IsMatch("lcfw=M16 AND gclj=M20", conditions) ' True
' OR 运算
Debug.Print matcher.IsMatch("lcfw=M16 OR lcfw=M17", conditions) ' True
' 括号优先级
Debug.Print matcher.IsMatch("gclj=M20 AND (lcfw=M16 OR lcfw=M17)", conditions) ' True
```
### 4. 获取物料清单
#### 指定类别
```vba
Dim bomMgr As New clsBOMManager
bomMgr.LoadData wsConfig, wsPlatform
' 获取"部件"类别的符合条件的物料
Dim materials As Collection
Set materials = bomMgr.GetMaterialsByModel("YTHN-100.A0.532.M203.M16.Y3", "部件")
```
#### 所有类别
```vba
' 获取所有类别的符合条件的物料
Set materials = bomMgr.GetMaterialsByModel("YTHN-100.A0.532.M203.M16.Y3")
```
#### 使用父类别/子类别逻辑
```vba
' 使用父类别物料(默认)
Set materials = bomMgr.GetMaterialsByCategoryAndModel( _
"YTHN-100.A0.532.M203.M16.Y3", "部件", True)
' 使用子类别物料(库存不足时)
Set materials = bomMgr.GetMaterialsByCategoryAndModel( _
"YTHN-100.A0.532.M203.M16.Y3", "部件", False)
```
---
## API参考
### clsModelParser
| 方法/属性 | 说明 | 返回类型 |
|---------|------|---------|
| `ParseModel(modelStr)` | 解析型号字符串 | Boolean |
| `GetThreadCode()` | 获取螺纹代码 | String |
| `GetMaterialCode()` | 获取材质代码 | String |
| `GetRangeCode()` | 获取量程代码 | String |
| `IsValid` | 解析是否成功 | Boolean |
| `ErrorMessage` | 错误信息 | String |
| `ToString()` | 返回解析结果(调试用) | String |
### clsConditionExtractor
| 方法/属性 | 说明 | 返回类型 |
|---------|------|---------|
| `ExtractConditions(parser)` | 提取所有条件 | Dictionary |
| `GetConditionValue(varName)` | 获取单个条件值 | String |
| `AddCondition(varName, value)` | 手动添加条件 | - |
| `AddExtractionRule(...)` | 添加提取规则 | - |
| `ToString()` | 返回条件字符串(调试用) | String |
### clsConditionMatcher
| 方法 | 说明 | 返回类型 |
|-----|------|---------|
| `IsMatch(expr, conditions)` | 判断条件是否匹配 | Boolean |
| `TestExpression(expr)` | 测试表达式有效性 | String |
### clsBOMManager (新增方法)
| 方法 | 说明 | 返回类型 |
|-----|------|---------|
| `GetMaterialsByModel(modelStr, [categoryName])` | 根据型号获取物料 | Collection |
| `GetMaterialsByCategoryAndModel(...)` | 根据类别和型号获取物料 | Collection |
| `ParseModelAndExtractConditions(modelStr)` | 解析型号并返回条件 | Dictionary |
---
## 扩展指南
### 添加新的提取条件
假设需要添加"表盘类型"条件:
```vba
' 在 clsConditionExtractor.InitializeRules 方法中添加:
m_ExtractionRules("bplx") = Array("DialCode", "Direct")
```
### 添加新的提取方法
如果需要特殊的提取逻辑:
```vba
' 在 clsConditionExtractor.ExtractValue 方法中添加:
Case "GetMiddleChars"
' 提取中间几位字符
If Len(sourceValue) > 4 Then
ExtractValue = Mid(sourceValue, 2, 3)
Else
ExtractValue = sourceValue
End If
```
### 扩展条件匹配运算符
`clsConditionMatcher.EvaluateSimpleCondition` 中添加新运算符:
```vba
' 示例: 添加 > 运算符
ElseIf InStr(cond, ">") > 0 Then
' ... 实现大于比较
```
---
## 常见问题
### Q1: 型号解析失败怎么办?
**A**: 检查型号格式是否正确:
- 表头部分必须包含: `型号-外径.安装.壳体.连接.量程.[特性]`
- 各部分用 `.` 分隔
- 表头和表盘用 `|` 分隔
### Q2: 条件匹配不准确?
**A**:
1. 检查物料的选择条件表达式是否正确
2. 运行 `Test3_ConditionMatcher` 验证匹配器功能
3. 使用 `TestExpression` 方法测试具体表达式
### Q3: 如何调试条件提取?
**A**: 使用以下代码查看提取结果:
```vba
Dim conditions As Object
Set conditions = bomMgr.ParseModelAndExtractConditions(modelStr)
Dim key As Variant
For Each key In conditions.Keys
Debug.Print key & " = " & conditions(key)
Next key
```
### Q4: 批量处理速度慢?
**A**:
1. 避免在循环中重复加载BOM数据
2. 使用 `GetMaterialsByModel` 一次性获取所有类别
3. 考虑添加缓存机制
### Q5: 如何验证物料条件表达式?
**A**: 运行示例:
```vba
Example5_ValidateMaterialConditions
```
这将生成一个验证报告,显示所有物料条件的有效性。
---
## 测试用例
### 运行所有测试
```vba
RunAllTests
```
### 单独测试
```vba
' 测试型号解析
Test1_ModelParser
' 测试条件提取
Test2_ConditionExtractor
' 测试条件匹配
Test3_ConditionMatcher
' 测试完整流程
Test4_GetMaterialsByModel
```
---
## 应用示例
### 示例1: 生成领料清单
```vba
Example1_GeneratePickingListByModel
```
### 示例2: 批量处理型号
```vba
Example2_BatchProcessModels
```
### 示例3: 查询型号详情
```vba
Example3_ShowModelDetails
```
### 示例4: 对比两个型号
```vba
Example4_CompareModels
```
### 示例5: 验证条件表达式
```vba
Example5_ValidateMaterialConditions
```
---
## 技术支持
如有问题,请:
1. 查看测试结果和调试信息
2. 运行相关示例程序
3. 检查条件表达式语法
---
**文档版本**: v1.0
**最后更新**: 2026-01-19

View File

@@ -1,11 +0,0 @@
| | A | B | C | D | E | F | G | H | I | J | K |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | 代号 | YTHN-100 | 描述 | | 英文名称 | | | | | | |
| 2 | 名称 | (不锈钢)耐震压力表 | 负责人 | | 备注 | | | | | | |
| 3 | 行号 | 模块 | 代号 | 名称 | 数量 | 选择条件 | 备注 | 类别 | 上层类别 | 类别选用条件 | 66代码 |
| 4 | 10 | 316L部件 | 01011009557 | 径向低压接头部件 | 1.0 | gclj=M20 AND jycz=1 AND lcfw=M01 AND (azxs=A0 OR azxs=AT OR azxs=AH) | | 部件 | | | 66021009557 |
| 5 | 20 | 316L部件 | 01011009520 | 径向低压接头部件 | 1.0 | gclj=M20 AND jycz=1 AND lcfw=M02 AND (azxs=A0 OR azxs=AT OR azxs=AH) | | 部件 | | | 66021009520 |
| 6 | 30 | 316L部件 | 01011013536 | 径向低压接头部件 | 1.0 | gclj=M20 AND jycz=1 AND lcfw=M03 AND (azxs=A0 OR azxs=AT OR azxs=AH) | | 部件 | | | 66021013536 |
| 7 | 40 | 316L部件 | 01011013538 | 径向低压接头部件 | 1.0 | gclj=M20 AND jycz=1 AND lcfw=M04 AND (azxs=A0 OR azxs=AT OR azxs=AH) | | 部件 | | | 66021013538 |
| 8 | 50 | 316L部件 | 01011009522 | 径向低压接头部件 | 1.0 | gclj=M20 AND jycz=1 AND lcfw=M05 AND (azxs=A0 OR azxs=AT OR azxs=AH) | | 部件 | | | 66021009522 |
| 9 | 60 | 316L部件 | 01011013336 | 径向低压接头部件 | 1.0 | gclj=M20 AND jycz=1 AND lcfw=M06 AND (azxs=A0 OR azxs=AT OR azxs=AH) | | 部件 | | | 66021013336 |

View File

@@ -1,276 +0,0 @@
# 新增条件提取功能实现计划
## 一、需求概述
需要向 `clsConditionExtractor.cls` 类增加两个新的条件提取:
1. **安装形式 (azxs)** - 从型号中提取安装形式代码
2. **表壳形式 (bkxs)** - 从型号中提取表壳形式代码
### 示例说明
以型号 `YTHN-100.A0.532.M203.M17.Y3|BP-095.2312.M17.P3` 为例:
- **A0** = 安装形式代码(径向无边)
- **532** = 表壳形式代码,其中:
- `53` = 表壳类型304外壳、安装式、可充油
- `2` = 罩壳类型(外卡式)
---
## 二、参考文档依据
根据 `布莱迪公司压力表产品选型大表.md` 文档:
### 2.1 安装形式代码第159-204行
| 代码 | 说明 |
|------|------|
| A0 | 径向无边 |
| AT | 径向前边 |
| AH | 径向后边 |
| AM | 膜片式径向 |
| Z0 | 中轴向无边 |
| B0 | 下轴向无边 |
| ... | 共20多种安装形式 |
**提取规则**直接提取2位代码字母+数字组合)
### 2.2 表壳形式代码第207-266行
表壳形式由两部分组成:**表壳类型(2位) + 罩壳类型(1位)**
#### 表壳类型示例:
| 代码 | 说明 |
|------|------|
| 00 | 特殊型 |
| 10 | 塑料表壳 |
| 20-24 | 碳钢喷塑外壳系列 |
| 30 | 坚固安全型 |
| 40 | 压铸铝喷塑外壳 |
| 50-55 | 304外壳系列 |
| 60-65 | 316L外壳系列 |
#### 罩壳类型示例:
| 代码 | 说明 |
|------|------|
| 0 | 螺钉上紧式 |
| 1 | 内卡式 |
| 2 | 外卡式 |
| 3 | 滚边式 |
| 4-9 | 其他6种罩壳类型 |
**提取规则**
- 整体提取3位代码532
- 如果需要细分,可以拆分为:
- 前2位表壳类型53
- 后1位罩壳类型2
---
## 三、技术实现方案
### 3.1 代码检查
检查 `clsModelParser` 类是否已包含以下属性:
-`InstallForm` - 安装形式代码
-`ShellForm` - 表壳形式代码
**说明**:根据 `clsConditionExtractor.cls` 第113-116行显示解析器已经支持这些字段。
### 3.2 提取规则设计
#### 规则1安装形式 (azxs)
```vba
m_ExtractionRules("azxs") = Array("InstallForm", "Direct")
```
- **源字段**`InstallForm`
- **提取方法**`Direct`(直接使用)
#### 规则2表壳形式 (bkxs)
```vba
m_ExtractionRules("bkxs") = Array("ShellForm", "Direct")
```
- **源字段**`ShellForm`
- **提取方法**`Direct`(直接使用)
### 3.3 修改位置
`clsConditionExtractor.cls``InitializeRules()` 方法中添加上述两条规则。
**修改位置**第29-47行InitializeRules 方法内部)
---
## 四、实现步骤
### 步骤1修改 clsConditionExtractor.cls
`InitializeRules()` 方法中添加两个新的提取规则:
```vba
Private Sub InitializeRules()
' 规则格式: Dictionary(变量名) = Array(源字段, 提取方法)
' 规则1: 过程连接 (gclj)
m_ExtractionRules("gclj") = Array("ConnectionCode", "RemoveLastDigit")
' 规则2: 接液材质 (jycz)
m_ExtractionRules("jycz") = Array("ConnectionCode", "GetLastDigit")
' 规则3: 量程范围 (lcfw)
m_ExtractionRules("lcfw") = Array("RangeCode", "Direct")
' === 新增规则 ===
' 规则4: 安装形式 (azxs)
m_ExtractionRules("azxs") = Array("InstallForm", "Direct")
' 规则5: 表壳形式 (bkxs)
m_ExtractionRules("bkxs") = Array("ShellForm", "Direct")
End Sub
```
### 步骤2验证 ExtractValue 方法
检查 `ExtractValue` 方法第98-157行是否已支持 `InstallForm``ShellForm` 源字段。
**确认点**
- 第113-114行`Case "InstallForm"` ✅ 已存在
- 第115-116行`Case "ShellForm"` ✅ 已存在
**结论**:无需修改 `ExtractValue` 方法,它已经支持所需的源字段。
### 步骤3测试验证
创建或更新测试用例,验证提取功能:
```vba
' 测试用例
Dim parser As New clsModelParser
Dim extractor As New clsConditionExtractor
Dim conditions As Object
' 解析型号
parser.Parse "YTHN-100.A0.532.M203.M17.Y3|BP-095.2312.M17.P3"
' 提取条件
Set conditions = extractor.ExtractConditions(parser)
' 验证结果
Debug.Print "安装形式(azxs): " & conditions("azxs") ' 应输出: A0
Debug.Print "表壳形式(bkxs): " & conditions("bkxs") ' 应输出: 532
```
---
## 五、影响范围评估
### 5.1 影响的文件
| 文件 | 修改类型 | 说明 |
|------|----------|------|
| `VBA/ClassModules/clsConditionExtractor.cls` | 修改 | 添加2条提取规则 |
### 5.2 兼容性
-**向后兼容**新增规则不影响现有条件gclj、jycz、lcfw的提取
-**现有测试**:无需修改现有测试用例
- ⚠️ **物料匹配**如需在物料选择条件中使用这些新条件需在Excel表格的"选择条件"列中使用新变量名azxs、bkxs
### 5.3 使用场景
新增的条件可以在以下场景中使用:
1. **物料选择条件表达式**
```
azxs=A0 AND bkxs=532
```
2. **BOM完整性检查**
- 检查是否缺少特定安装形式的物料
- 检查是否缺少特定表壳形式的物料
3. **数据分析和统计**
- 按安装形式统计物料需求
- 按表壳形式统计物料需求
---
## 六、可选增强功能(未来扩展)
### 6.1 表壳形式细分提取
如果需要将表壳形式细分为表壳类型和罩壳类型,可以添加:
```vba
' 表壳类型前2位
m_ExtractionRules("bklx") = Array("ShellForm", "GetFirstTwoDigits")
' 罩壳类型后1位
m_ExtractionRules("zklx") = Array("ShellForm", "GetLastDigit")
```
同时需要在 `ExtractValue` 方法中添加新的提取方法:
- `GetFirstTwoDigits`取前2位字符
- `GetLastDigit`:已存在,直接复用
### 6.2 条件验证
可以添加条件值验证功能,确保提取的值在合法范围内:
```vba
Public Function ValidateCondition(varName As String, value As String) As Boolean
' 验证安装形式是否合法
' 验证表壳形式是否合法
End Function
```
---
## 七、实施清单
- [ ] 1. 备份 `clsConditionExtractor.cls` 文件
- [ ] 2. 在 `InitializeRules()` 方法中添加 azxs 提取规则
- [ ] 3. 在 `InitializeRules()` 方法中添加 bkxs 提取规则
- [ ] 4. 更新代码注释,说明新增规则的用途
- [ ] 5. 创建测试用例验证提取功能
- [ ] 6. 运行现有测试套件,确保无回归问题
- [ ] 7. 更新相关文档(如需要)
- [ ] 8. 提交代码变更
---
## 八、预期结果
实现完成后,系统将能够:
1. ✅ 从产品型号中自动提取安装形式代码azxs
2. ✅ 从产品型号中自动提取表壳形式代码bkxs
3. ✅ 在物料选择条件表达式中使用这两个新变量
4. ✅ 保持向后兼容,不影响现有功能
**测试验证示例**
输入型号:`YTHN-100.A0.532.M203.M17.Y3`
提取结果:
- `gclj` = M20
- `jycz` = 3
- `lcfw` = M17
- **`azxs` = A0** ⬅️ 新增
- **`bkxs` = 532** ⬅️ 新增
---
## 九、备注
1. 本计划遵循现有代码的设计模式和命名规范
2. 所有新增代码将包含中文注释,便于维护
3. 修改完成后将更新 VBA 模块元数据(如需要)
4. 建议在Excel开发环境中先进行手动测试确保功能正常
---
**文档版本**: 1.0
**创建日期**: 2026-01-27
**作者**: Claude Code

View File

@@ -1,650 +0,0 @@
# 重构计划:整合 [领料配置] 到 [平台配置清单]
## 一、背景概述
### 1.1 重构目标
将原有的 [领料配置] 表格的功能整合到 [平台配置清单] 中,简化数据结构,统一管理。
### 1.2 变化原因
- 原有设计:物料基础信息在 [平台配置清单],类别配置在 [领料配置],数据分散
- 新设计:所有信息集中在 [平台配置清单] 一张表,便于维护
---
## 二、新工作表结构分析
### 2.1 新表结构
**[平台配置清单] 表结构**第3行标题
| 列 | 字段名 | 说明 | 示例 |
|---|--------|------|------|
| A | 行号 | 数据行序号 | 10, 20, 30 |
| B | 模块 | 模块分类 | 316L部件 |
| C | 代号 | 物料代号 | 01011009557 |
| D | 名称 | 物料名称 | 径向低压接头部件 |
| E | 数量 | 物料数量 | 1.0 |
| F | 选择条件 | 物料选用条件 | gclj=M20 AND jycz=1 |
| G | 备注 | 备注信息 | |
| **H** | **类别** | ⭐ 新增:物料所属类别 | 部件 |
| **I** | **上层类别** | ⭐ 新增:父类别名称 | |
| **J** | **类别选用条件** | ⭐ 新增:判断该类别是否需要 | lcfw=M02 |
| **K** | **66代码** | ⭐ 新增66系统代码 | 66021009557 |
### 2.2 字段说明
#### 新增字段详解
1. **类别H列**
- 类型:字符串
- 说明:物料所属的类别名称
- 示例:`部件``表壳``接头``机芯`
- 为空:表示该物料不属于任何类别(不需要领料)
2. **上层类别I列**
- 类型:字符串
- 说明:父类别的名称,用于构建类别层级树
- 示例:`接头` 的上层类别是 `部件`
- 为空:表示该类别是根类别
3. **类别选用条件J列****关键字段**
- 类型:字符串(逻辑表达式)
- 说明:用于判断该类别是否需要被选用
- 格式:与"选择条件"字段相同的逻辑表达式
- 判断规则:
- **为空**:该类别总是需要(无条件)
- **不为空**:需要用型号提取的条件去匹配,匹配成功才需要
- 示例:
- `lcfw=M02`:只有当量程是 M02 时才需要这个类别
- `gclj=M20 AND jycz=1`:只有当过程连接是 M20 且接液材质是 1 时才需要
4. **66代码K列**
- 类型:字符串
- 说明66系统使用的代码具体用途待补充
- 示例:`66021009557`
---
## 三、核心业务逻辑变化
### 3.1 旧逻辑(重构前)
```
有效性检查规则:
- 所有根类别都必须匹配到至少1个物料
- 每个根类别或其子类别必须有且仅有1个物料匹配
- 如果有任何一个类别不满足,则物料集合不完整
```
**问题**
- 无法表达"某些型号不需要某些类别"的情况
- 例如:某些型号可能不需要"机芯"类别
### 3.2 新逻辑(重构后)
```
有效性检查规则:
- 第一步:确定哪些类别是该型号需要的
- 遍历所有有物料的类别
- 如果"类别选用条件"为空 → 该类别总是需要
- 如果"类别选用条件"不为空 → 用型号条件去匹配,匹配成功才需要
- 第二步:只检查需要的类别是否有物料匹配
- 对于需要的类别必须有且仅有1个物料匹配
- 不需要的类别不参与完整性检查
```
**示例场景**
型号:`YTHN-100.A0.532.M203.M16.Y3` (量程 M16)
| 类别 | 类别选用条件 | 是否需要 | 说明 |
|------|-------------|---------|------|
| 表壳 | (空) | ✅ 需要 | 无条件,总是需要 |
| 部件 | lcfw=M02 | ❌ 不需要 | 型号量程是 M16不满足条件 |
| 接头 | lcfw=M16 | ✅ 需要 | 型号量程是 M16满足条件 |
| 机芯 | gclj=M20 | ❌ 不需要 | 型号过程连接不是 M20 |
**结果**:只需要检查 `表壳``接头` 类别的物料完整性
---
## 四、代码影响分析
### 4.1 需要修改的模块和方法
#### 1. clsBOMManager 类 ⭐ **核心改动**
**1.1 LoadData 方法**
```vba
' 旧签名
Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet)
' 新签名
Public Sub LoadData(wsPlatform As Worksheet)
```
**变化**
- ❌ 移除 `wsConfig` 参数(不再需要 [领料配置] 表)
- ✅ 所有数据从 [平台配置清单] 读取
- ✅ 读取列数增加:从 A-F 扩展到 A-K
**读取列对应**
```vba
' 第一步:加载物料和类别信息(一次性完成)
For i = 4 To lastRow
' 基础信息
mat.code = Trim(wsPlatform.Cells(i, "C").value) ' 代号
mat.Name = Trim(wsPlatform.Cells(i, "D").value) ' 名称
mat.Quantity = CDbl(wsPlatform.Cells(i, "E").value) ' 数量
mat.Condition = Trim(wsPlatform.Cells(i, "F").value) ' 选择条件
' ⭐ 新增:类别相关字段
catName = Trim(wsPlatform.Cells(i, "H").value) ' 类别
parentCatName = Trim(wsPlatform.Cells(i, "I").value) ' 上层类别
catSelectCond = Trim(wsPlatform.Cells(i, "J").value) ' 类别选用条件
code66 = Trim(wsPlatform.Cells(i, "K").value) ' 66代码
Next i
```
**流程变化**
```
旧流程(三步):
1. 从 [平台配置清单] 加载物料基础信息
2. 从 [领料配置] 加载类别信息
3. 建立类别层级关系
新流程(两步):
1. 从 [平台配置清单] 加载物料和类别信息(一次性)
2. 建立类别层级关系
```
---
**1.2 GetValidMaterialsByModel 方法**
**变化**
- ✅ 需要先判断哪些类别是该型号需要的
- ✅ 只检查需要的类别的完整性
**新逻辑**
```vba
Public Function GetValidMaterialsByModel(modelStr As String) As collection
' 1. 解析型号并提取条件
Set conditions = ParseModelAndExtractConditions(modelStr)
' 2. ⭐ 新增:确定需要的类别列表
Dim requiredCategories As collection
Set requiredCategories = GetRequiredCategories(conditions)
' 3. 只检查需要的类别
For Each cat In requiredCategories
Set catResult = CheckCategoryCompleteness(cat, matcher, conditions)
' ...
Next cat
End Function
```
---
**1.3 GetRequiredCategories 方法(新增)**
**功能**:根据型号条件,确定哪些类别是需要的
```vba
Private Function GetRequiredCategories(conditions As Object) As collection
' 遍历所有类别
' - 类别选用条件为空 → 添加到需要列表
' - 类别选用条件不为空 → 用 conditions 去匹配
' - 匹配成功 → 添加到需要列表
' - 匹配失败 → 不添加
End Function
```
---
**1.4 CheckCategoryCompleteness 方法**
**变化**
- 无需修改,因为调用者已经过滤了不需要的类别
- 逻辑保持不变检查需要的类别是否有且仅有1个物料匹配
---
#### 2. clsCategory 类 ⭐ **需要扩展**
**新增属性**
```vba
Public CategorySelectCondition As String ' ⭐ 新增:类别选用条件
Public IsRequired As Boolean ' ⭐ 新增:是否需要(计算属性)
```
**新增方法**
```vba
' 检查该类别是否需要(根据型号条件)
Public Function IsRequiredForModel(conditions As Object) As Boolean
If Me.CategorySelectCondition = "" Then
' 选用条件为空,总是需要
IsRequiredForModel = True
Else
' 需要用 conditions 去匹配 CategorySelectCondition
Dim matcher As New clsConditionMatcher
IsRequiredForModel = matcher.IsMatch(Me.CategorySelectCondition, conditions)
End If
End Function
```
---
#### 3. clsMaterialItem 类 ⭐ **需要扩展**
**新增属性**
```vba
Public CategorySelectCondition As String ' ⭐ 新增:类别选用条件
Public Code66 As String ' ⭐ 新增66代码
```
**说明**
- 物料需要保存所属类别的"类别选用条件"
- 便于后续判断该物料是否需要
---
### 4.2 不需要修改的方法
以下方法不受影响,无需修改:
-`GetRootCategories()` - 获取根类别
-`GetCategory(categoryName)` - 获取类别对象
-`GetMaterialsByModel()` - 根据型号获取物料(不涉及完整性检查)
-`GetMaterialsForPicking()` - 获取领料物料
-`ParseModelAndExtractConditions()` - 解析型号
-`FilterCategoryWithSubcategories()` - 类别筛选
-`CollectAllMaterials()` - 收集物料
-`PrintCategoryTree()` - 打印类别树
---
## 五、重构实施计划
### 5.1 实施步骤
#### 阶段一数据模型扩展1-2小时
**任务清单**
1.**修改 clsMaterialItem 类**
- [ ] 添加 `CategorySelectCondition` 属性
- [ ] 添加 `Code66` 属性
- [ ] 更新类注释
2.**修改 clsCategory 类**
- [ ] 添加 `CategorySelectCondition` 属性
- [ ] 添加 `IsRequiredForModel()` 方法
- [ ] 更新类注释
3.**添加测试用例**
- [ ] 测试类别选用条件的匹配逻辑
- [ ] 测试空条件和非空条件的情况
---
#### 阶段二LoadData 方法重构2-3小时
**任务清单**
1.**修改方法签名**
```vba
' 旧
Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet)
' 新
Public Sub LoadData(wsPlatform As Worksheet)
```
2. ✅ **重写加载逻辑**
- [ ] 移除第二步(从 [领料配置] 加载)
- [ ] 合并第一步和第二步(一次性从 [平台配置清单] 加载所有字段)
- [ ] 更新列索引映射:
- H列类别
- I列上层类别
- J列类别选用条件
- K列66代码
3. ✅ **更新类别创建逻辑**
- [ ] 创建类别时,设置 `CategorySelectCondition` 属性
- [ ] 创建物料时,设置 `CategorySelectCondition` 和 `Code66` 属性
4. ✅ **更新文档**
- [ ] 更新 `clsBOMManager_LoadData流程分析.md`
- [ ] 更新方法注释
---
#### 阶段三完整性检查逻辑重构2-3小时
**任务清单**
1. ✅ **新增 GetRequiredCategories 方法**
```vba
Private Function GetRequiredCategories(conditions As Object) As collection
' 遍历所有类别,判断是否需要
' 返回需要的类别列表
End Function
```
2. ✅ **修改 GetValidMaterialsByModel 方法**
- [ ] 在检查完整性前,先调用 `GetRequiredCategories()`
- [ ] 只对需要的类别调用 `CheckCategoryCompleteness()`
- [ ] 更新方法注释
3. ✅ **测试完整性检查**
- [ ] 测试所有类别都需要的情况(向后兼容)
- [ ] 测试部分类别不需要的情况(新逻辑)
- [ ] 测试边界情况(所有类别都不需要)
---
#### 阶段四测试与验证2-3小时
**任务清单**
1. ✅ **单元测试**
- [ ] 测试 `LoadData` 加载新表结构
- [ ] 测试 `IsRequiredForModel()` 方法
- [ ] 测试 `GetRequiredCategories()` 方法
- [ ] 测试 `GetValidMaterialsByModel()` 新逻辑
2. ✅ **集成测试**
- [ ] 使用真实数据测试完整流程
- [ ] 验证结果与预期一致
3. ✅ **回归测试**
- [ ] 运行 `modModelParserTest` 中的所有测试
- [ ] 确保旧功能不受影响
---
#### 阶段五文档更新1小时
**任务清单**
1. ✅ **更新 CLAUDE.md**
- [ ] 更新 [平台配置清单] 表结构说明
- [ ] 移除 [领料配置] 相关说明
- [ ] 更新"类别选用条件"业务逻辑说明
2. ✅ **更新参考文档**
- [ ] 更新 `clsBOMManager使用说明.md`
- [ ] 更新 `clsBOMManager_LoadData流程分析.md`
3. ✅ **添加迁移指南**
- [ ] 创建 `数据迁移指南.md`
- [ ] 说明如何将旧数据迁移到新结构
---
### 5.2 风险评估
| 风险项 | 风险等级 | 缓解措施 |
|--------|---------|---------|
| 数据结构变化导致现有代码报错 | 🔴 高 | 充分的单元测试和回归测试 |
| 类别选用条件逻辑理解偏差 | 🟡 中 | 详细的业务场景测试 |
| 向后兼容性问题 | 🟡 中 | 保留旧版本的 LoadData 方法(重命名为 LoadDataV1 |
| 性能影响 | 🟢 低 | 新逻辑只增加一次类别过滤,影响极小 |
---
### 5.3 时间估算
| 阶段 | 预计时间 | 负责人 |
|------|---------|--------|
| 阶段一:数据模型扩展 | 1-2 小时 | |
| 阶段二LoadData 重构 | 2-3 小时 | |
| 阶段三:完整性检查重构 | 2-3 小时 | |
| 阶段四:测试与验证 | 2-3 小时 | |
| 阶段五:文档更新 | 1 小时 | |
| **总计** | **8-12 小时** | |
---
## 六、数据迁移方案
### 6.1 从旧表到新表
**旧 [领料配置] 结构**
| A列 | B列 | C列 | D列 |
|-----|-----|-----|-----|
| 物料代号 | 物料名称 | 类别 | 上层类别 |
**新 [平台配置清单] 结构**
| ... | H列 | I列 | J列 | K列 |
|-----|-----|-----|-----|-----|
| ... | 类别 | 上层类别 | 类别选用条件 | 66代码 |
**迁移步骤**
1. **在 [平台配置清单] 中添加新列**
- H列类别
- I列上层类别
- J列类别选用条件
- K列66代码
2. **从 [领料配置] 复制数据**
```
H2 = [领料配置]!A2 ' 物料代号 → 类别(需要匹配)
I2 = [领料配置]!C2 ' 类别
J2 = [领料配置]!D2 ' 上层类别
```
3. **使用 VLOOKUP 匹配物料**
```excel
H4 = VLOOKUP(C4, [领料配置]!A:D, 3, FALSE) ' 类别
I4 = VLOOKUP(C4, [领料配置]!A:D, 4, FALSE) ' 上层类别
```
4. **手动填写类别选用条件和66代码**
---
## 七、测试用例设计
### 7.1 类别选用条件测试
| 测试场景 | 类别选用条件 | 型号条件 | 预期结果 |
|---------|-------------|---------|---------|
| 无条件(总是需要) | (空) | 任意 | ✅ 需要 |
| 量程匹配 | lcfw=M02 | lcfw=M02 | ✅ 需要 |
| 量程不匹配 | lcfw=M02 | lcfw=M16 | ❌ 不需要 |
| 复杂条件匹配 | gclj=M20 AND jycz=1 | gclj=M20, jycz=1 | ✅ 需要 |
| 复杂条件不匹配 | gclj=M20 AND jycz=1 | gclj=M20, jycz=2 | ❌ 不需要 |
| OR 条件匹配 | lcfw=M02 OR lcfw=M16 | lcfw=M16 | ✅ 需要 |
### 7.2 完整性检查测试
| 测试场景 | 需要的类别 | 匹配结果 | 预期 IsComplete |
|---------|-----------|---------|-----------------|
| 全部匹配 | 表壳, 部件, 接头 | 全部有1个 | ✅ True |
| 缺失一个 | 表壳, 部件, 接头 | 部件缺失 | ❌ False |
| 部分类别不需要 | 表壳, 部件 | 表壳有, 部件无(但部件不需要) | ✅ True |
| 多于一个匹配 | 表壳, 部件 | 部件有2个 | ❌ False |
---
## 八、向后兼容性策略
### 8.1 保留旧版本方法
```vba
' 旧版本(保留但不推荐使用)
Public Sub LoadDataV1(wsConfig As Worksheet, wsPlatform As Worksheet)
' 保留原有实现
End Sub
' 新版本(推荐使用)
Public Sub LoadData(wsPlatform As Worksheet)
' 新实现
End Sub
```
### 8.2 迁移期支持
在迁移期间,两个版本同时存在:
- 新代码使用 `LoadData(wsPlatform)`
- 旧代码可以继续使用 `LoadDataV1(wsConfig, wsPlatform)`
迁移完成后,可以移除 `LoadDataV1` 方法。
---
## 九、检查清单
### 重构前
- [ ] 备份现有代码
- [ ] 备份 [领料配置] 和 [平台配置清单] 表
- [ ] 确认测试环境可用
### 重构中
- [ ] 按阶段顺序执行
- [ ] 每个阶段完成后进行测试
- [ ] 及时更新文档
### 重构后
- [ ] 运行完整的测试套件
- [ ] 验证所有功能正常
- [ ] 更新用户文档
- [ ] 通知团队成员变更内容
---
## 十、后续优化建议
1. **性能优化**
- 缓存类别选用条件的匹配结果
- 避免重复解析型号
2. **功能扩展**
- 支持类别优先级(当多个类别都匹配时,优先选择哪个)
- 支持类别别名(一个类别多个名称)
3. **用户体验**
- 在 Excel 中添加类别选用条件的验证工具
- 提供类别选用条件的构建器(图形化界面)
---
## 附录:关键代码示例
### A. 修改后的 LoadData 方法(伪代码)
```vba
Public Sub LoadData(wsPlatform As Worksheet)
Dim i As Long, lastRow As Long
Dim mat As clsMaterialItem
Dim cat As clsCategory
' 读取数据
lastRow = wsPlatform.Cells(wsPlatform.Rows.Count, "C").End(xlUp).row
For i = 4 To lastRow
' 1. 创建物料对象
Set mat = New clsMaterialItem
mat.code = Trim(wsPlatform.Cells(i, "C").value)
mat.Name = Trim(wsPlatform.Cells(i, "D").value)
mat.Quantity = CDbl(wsPlatform.Cells(i, "E").value)
mat.Condition = Trim(wsPlatform.Cells(i, "F").value)
' ⭐ 读取类别相关字段
Dim catName As String
Dim parentCatName As String
Dim catSelectCond As String
Dim code66 As String
catName = Trim(wsPlatform.Cells(i, "H").value & "")
parentCatName = Trim(wsPlatform.Cells(i, "I").value & "")
catSelectCond = Trim(wsPlatform.Cells(i, "J").value & "")
code66 = Trim(wsPlatform.Cells(i, "K").value & "")
' ⭐ 保存到物料对象
mat.Category = catName
mat.ParentCategory = parentCatName
mat.CategorySelectCondition = catSelectCond
mat.Code66 = code66
' 保存到字典
If mat.code <> "" Then
Set dictAllMaterials(mat.code) = mat
End If
' ⭐ 创建或更新类别对象
If catName <> "" Then
If Not dictCategories.Exists(catName) Then
Set cat = New clsCategory
cat.categoryName = catName
cat.ParentCategoryName = parentCatName
cat.CategorySelectCondition = catSelectCond ' ⭐ 新增
Set dictCategories(catName) = cat
End If
' 将物料添加到类别
dictCategories(catName).AddMaterial mat
End If
Next i
' 建立类别层级关系(逻辑不变)
' ...
End Sub
```
### B. 新增 GetRequiredCategories 方法(伪代码)
```vba
Private Function GetRequiredCategories(conditions As Object) As collection
Dim result As New collection
Dim matcher As New clsConditionMatcher
' 遍历所有类别
Dim key As Variant
For Each key In dictCategories.Keys
Dim cat As clsCategory
Set cat = dictCategories(key)
' 检查该类别是否需要
If cat.IsRequiredForModel(conditions) Then
result.Add cat
End If
Next key
Set GetRequiredCategories = result
End Function
```
### C. 新增 clsCategory.IsRequiredForModel 方法(伪代码)
```vba
Public Function IsRequiredForModel(conditions As Object) As Boolean
If Me.CategorySelectCondition = "" Then
' 选用条件为空,总是需要
IsRequiredForModel = True
Else
' 需要用 conditions 去匹配 CategorySelectCondition
Dim matcher As New clsConditionMatcher
IsRequiredForModel = matcher.IsMatch(Me.CategorySelectCondition, conditions)
End If
End Function
```
---
**文档版本**v1.0
**创建日期**2026-01-29
**最后更新**2026-01-29