diff --git a/CLAUDE.md b/CLAUDE.md index c9f2a6b..7fc82a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,17 +65,33 @@ Model String → Parser → Extractor → Conditions Dictionary → Matcher → ## Data Sheets Structure -The Excel workbook contains two key sheets: - -**领料配置** (Picking Configuration) -- Defines category hierarchy -- Specifies which materials require picking -- Columns: 物料代号, 类别名称, 上层类别名称 +The Excel workbook contains one main configuration sheet: **平台配置清单** (Platform Configuration) -- Complete material database -- Rows 1-3: Headers -- Row 4+: Data +- Complete material database with category hierarchy +- All configuration data in one sheet (simplified from previous two-sheet design) +- Row 1-3: Headers and product info +- Row 4+: Material data + +**Column Structure** (Row 3): +| Column | Field Name | Description | Example | +|--------|-----------|-------------|---------| +| A | 行号 | Line number | 10, 20, 30 | +| B | 模块 | Module name | 316L部件 | +| C | 代号 | Material code | 01011009557 | +| D | 名称 | Material name | 径向低压接头部件 | +| E | 数量 | Quantity | 1.0 | +| F | 选择条件 | Material selection condition | gclj=M20 AND jycz=1 | +| G | 备注 | Remarks | | +| H | 类别 | Category name | 部件 | +| I | 上层类别 | Parent category name | (empty for root categories) | +| J | 类别选用条件 | Category selection condition | lcfw=M02 | +| K | 66代码 | 66-system code | 66021009557 | + +**Category Selection Condition (类别选用条件)**: +- Empty: Category is always required +- Not empty: Category is required only when the condition matches the model specifications +- Uses same syntax as material selection conditions (AND, OR, !=, parentheses) ## Testing @@ -120,7 +136,11 @@ Run tests from the VBA editor or Excel macros: ## Key Business Logic - Materials have "selection conditions" expressed as logical formulas -- Not all materials in platform config require picking (filtered by "领料配置" sheet) +- Categories have "category selection conditions" to determine if they are required for a given model +- **Completeness Check Logic**: + - First, determine which categories are required by matching "类别选用条件" with model specifications + - Then, check if each required category has exactly one matching material + - Categories not required for the model are excluded from completeness check - Supports "部件" (component) vs "零件" (part) picking strategies - Condition extraction is data-driven (new variables can be added) - Category tree can have arbitrary depth diff --git a/VBA/ClassModules/clsBOMManager.cls b/VBA/ClassModules/clsBOMManager.cls index cbea360..c60e885 100644 --- a/VBA/ClassModules/clsBOMManager.cls +++ b/VBA/ClassModules/clsBOMManager.cls @@ -31,33 +31,44 @@ End Sub ' ======================================== ' LoadData 方法 -' 功能: 从Excel工作表加载BOM数据并构建数据结构 +' 功能: 从[平台配置清单]工作表加载BOM数据并构建数据结构 ' 参数: -' wsConfig - [领料配置]工作表对象,包含类别层级和需要领料的物料 -' wsPlatform - [平台配置清单]工作表对象,包含完整的物料信息(代号/名称/数量/条件) +' wsPlatform - [平台配置清单]工作表对象,包含完整的物料信息和类别配置 +' 表结构: 行号|模块|代号|名称|数量|选择条件|备注|类别|上层类别|类别选用条件|66代码 +' 数据从第4行开始(前3行是标题) ' 处理步骤: -' 1. 从[平台配置清单]加载所有物料的基础信息到dictAllMaterials -' 2. 从[领料配置]加载类别信息,筛选需要领料的物料 -' 3. 建立类别的父子关系,构建层级树 -' 注意: -' - 只有出现在[领料配置]中的物料才会被添加到类别中 -' - 未在[领料配置]中的物料表示不需要领料 +' 1. 从[平台配置清单]一次性加载所有物料信息和类别信息 +' 2. 建立类别的父子关系,构建层级树 +' +' 重要说明: +' - 所有数据都从[平台配置清单]读取,不再需要[领料配置]表 +' - 只有"类别"字段不为空的物料才会被添加到类别中 +' - "类别选用条件"用于判断该类别是否需要(为空表示总是需要) +' - "66代码"是66系统使用的代码 ' ======================================== -Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet) +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列(选择条件) + ' 读取列说明: + ' - C列(代号)、D列(名称)、E列(数量)、F列(选择条件) + ' - H列(类别)、I列(上层类别)、J列(类别选用条件)、K列(66代码) ' - 从第4行开始读取(前3行是标题) - ' - 所有物料存入dictAllMaterials字典,以代号为键 - ' 目的: 建立完整的物料信息库,供后续按代号查询 + ' + ' 处理逻辑: + ' 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 & "") ' 物料名称 @@ -66,55 +77,46 @@ Public Sub LoadData(wsConfig As Worksheet, wsPlatform As Worksheet) 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 ' ======================================== - ' 第二步: 从领料配置加载类别信息并建立层级 - ' 说明: - ' - 读取A列(代号)、C列(类别)、D列(上层类别) - ' - 从第2行开始读取(第1行是标题) - ' - 只有出现在此表中的物料才需要领料 - ' 处理逻辑: - ' 1. 为每个类别创建clsCategory对象 - ' 2. 从dictAllMaterials中查找对应的物料信息 - ' 3. 将物料添加到对应的类别中 - ' ======================================== - lastRow = wsConfig.Cells(wsConfig.Rows.Count, "A").End(xlUp).row - For i = 2 To lastRow ' 从第2行开始 - Dim code As String, catName As String, parentCatName As String - code = Trim(wsConfig.Cells(i, "A").value & "") ' 物料代号 - catName = Trim(wsConfig.Cells(i, "C").value & "") ' 类别名称 - parentCatName = Trim(wsConfig.Cells(i, "D").value & "") ' 上层类别名称 - - ' 跳过空行 - If code = "" Then GoTo NextRow - - ' 确保类别对象存在(如果类别不存在则创建) - If Not dictCategories.Exists(catName) Then - Set cat = New clsCategory - cat.categoryName = catName - cat.ParentCategoryName = parentCatName - Set dictCategories(catName) = cat - End If - - ' 将物料添加到类别 - ' 注意: 必须先在dictAllMaterials中查找到完整的物料信息 - If dictAllMaterials.Exists(code) Then - Set mat = dictAllMaterials(code) - mat.Category = catName ' 设置物料所属类别 - mat.ParentCategory = parentCatName ' 设置物料的上层类别 - dictCategories(catName).AddMaterial mat ' 将物料添加到类别对象中 - End If - -NextRow: - Next i - - ' ======================================== - ' 第三步: 建立类别层级关系 + ' 第二步: 建立类别层级关系 ' 说明: ' - 遍历所有类别,根据ParentCategoryName建立父子关系 ' - 如果类别有父类别,将自己添加到父类别的SubCategories中 @@ -662,6 +664,56 @@ Public Function GetMaterialsByCategoryAndModel(modelStr As String, _ 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 方法 ' 功能: 解析型号并返回条件字典(工具方法) @@ -689,23 +741,41 @@ End Function ' 参数: ' modelStr - 产品型号字符串 ' 返回: Collection对象 -' Collection中包含两个元素: +' Collection中包含三个元素: ' (1) "Materials" - Collection对象,包含所有匹配的 clsMaterialItem 对象 ' (2) "IsComplete" - Boolean值,指示物料是否完整 ' (3) "MissingCategories" - Collection对象, 包含所有缺失的类别名称字符串 ' -' 完整性判断规则: -' - 对于每个需要领料的类别(顶层类别或其需要领取的子类别) -' - 必须有且仅有一个物料被匹配 -' - 如果某个类别有0个或多于1个物料,则视为不完整 +' ⭐ 完整性判断规则(重构后): +' 第一步: 确定哪些类别是该型号需要的 +' - 遍历所有类别,检查"类别选用条件" +' - 类别选用条件为空 → 该类别总是需要 +' - 类别选用条件不为空 → 用型号条件匹配,匹配成功才需要 +' +' 第二步: 只检查需要的类别的完整性 +' - 对于每个需要的类别(顶层类别或其需要领取的子类别) +' - 必须有且仅有一个物料被匹配 +' - 如果某个需要的类别有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 @@ -732,17 +802,21 @@ Public Function GetValidMaterialsByModel(modelStr As String) As collection ' 条件匹配器 Dim matcher As New clsConditionMatcher - ' 遍历所有根类别,检查完整性 + ' ⭐ 第一步: 获取需要的类别列表 + Dim requiredCats As collection + Set requiredCats = GetRequiredCategories(conditions) + + ' ⭐ 第二步: 只检查需要的类别的完整性 isComplete = True - Dim rootCat As clsCategory + Dim reqCat As clsCategory Dim i As Long - For i = 1 To rootCategories.Count - Set rootCat = rootCategories(i) + For i = 1 To requiredCats.Count + Set reqCat = requiredCats(i) ' 检查该类别及其子类别的完整性 Dim catResult As Object - Set catResult = CheckCategoryCompleteness(rootCat, matcher, conditions) + Set catResult = CheckCategoryCompleteness(reqCat, matcher, conditions) ' 1. 合并匹配到的物料 Dim mat As clsMaterialItem @@ -752,7 +826,7 @@ Public Function GetValidMaterialsByModel(modelStr As String) As collection allMaterials.Add mat Next mat - ' 2. 合并缺失的类别 (新增) + ' 2. 合并缺失的类别 Dim missingCollection As collection Set missingCollection = catResult("Missing") Dim missingCatName As Variant @@ -769,7 +843,7 @@ Public Function GetValidMaterialsByModel(modelStr As String) As collection ' 返回结果 result.Add allMaterials, "Materials" result.Add isComplete, "IsComplete" - result.Add allMissingCats, "MissingCategories" ' 新增返回项 + result.Add allMissingCats, "MissingCategories" Set GetValidMaterialsByModel = result End Function diff --git a/VBA/ClassModules/clsCategory.cls b/VBA/ClassModules/clsCategory.cls index 23e4130..2ae7ea1 100644 --- a/VBA/ClassModules/clsCategory.cls +++ b/VBA/ClassModules/clsCategory.cls @@ -6,9 +6,10 @@ 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 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 @@ -37,4 +38,47 @@ 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 \ No newline at end of file diff --git a/VBA/ClassModules/clsMaterialItem.cls b/VBA/ClassModules/clsMaterialItem.cls index a87aa79..00c7b95 100644 --- a/VBA/ClassModules/clsMaterialItem.cls +++ b/VBA/ClassModules/clsMaterialItem.cls @@ -4,9 +4,11 @@ ' ======================================== 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 ' 上层类别 \ No newline at end of file +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系统代码 \ No newline at end of file diff --git a/reference_docs/clsBOMManager_LoadData流程分析-v2(整合后).md b/reference_docs/clsBOMManager_LoadData流程分析-v2(整合后).md new file mode 100644 index 0000000..9fcf6ff --- /dev/null +++ b/reference_docs/clsBOMManager_LoadData流程分析-v2(整合后).md @@ -0,0 +1,530 @@ +# 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[读取物料基础信息
C列:代号, D列:名称
E列:数量, F列:选择条件] + S1_ReadMat --> S1_ReadCat[读取类别信息
H列:类别, I列:上层类别
J列:类别选用条件, K列:66代码] + S1_ReadCat --> S1_CreateMat[创建 clsMaterialItem 对象] + S1_CreateMat --> S1_SetMatProps[设置物料属性
code, Name, Quantity, Condition
Category, ParentCategory
CategorySelectCondition, Code66] + S1_SetMatProps --> S1_SaveMat[保存到 dictAllMaterials] + S1_SaveMat --> S1_CheckCat{类别字段
是否为空?} + S1_CheckCat -->|否, 有类别| S1_EnsureCat{类别对象
是否存在?} + S1_CheckCat -->|是, 无类别| S1_Next + S1_EnsureCat -->|不存在| S1_CreateCat[创建新类别对象
设置 categoryName, ParentCategoryName
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 +├── "01011009557" → clsMaterialItem{ +│ code: "01011009557" +│ Name: "径向低压接头部件" +│ Quantity: 1.0 +│ Condition: "gclj=M20 AND jycz=1" +│ Category: "部件" +│ ParentCategory: "" +│ CategorySelectCondition: "" +│ Code66: "66021009557" +│ } +└── ... + +dictCategories: Dictionary +├── "部件" → clsCategory{ +│ categoryName: "部件" +│ ParentCategoryName: "" +│ CategorySelectCondition: "" +│ materials: Collection +│ SubCategories: Collection +│ } +└── ... +``` + +**关键代码**: +```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
所有键} + S2_Loop -->|下一个键| S2_GetCat[获取类别对象 cat] + S2_GetCat --> S2_CheckParent{ParentCategoryName
是否为空?} + S2_CheckParent -->|否, 有父类别| S2_ParentExists{父类别
存在?} + S2_CheckParent -->|是, 无父类别| S2_AddRoot[添加到 rootCategories
作为根类别] + S2_ParentExists -->|是| S2_AddSub[添加到父类别的
SubCategories 集合] + S2_ParentExists -->|否| S2_Orphan[父类别不存在
跳过建立关系] + 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 +├── "表壳" (根类别) +│ └── 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代码字段 + +最终形成的数据结构支持: +- 快速按代号查找物料 +- 快速按类别查找物料 +- 遍历完整的类别树 +- 根据型号动态确定需要的类别 +- 支持父类别/子类别的领料逻辑 diff --git a/reference_docs/平台配置清单.md b/reference_docs/平台配置清单.md new file mode 100644 index 0000000..2b71a9b --- /dev/null +++ b/reference_docs/平台配置清单.md @@ -0,0 +1,11 @@ +| | 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 | \ No newline at end of file diff --git a/reference_docs/重构计划-整合领料配置到平台配置清单.md b/reference_docs/重构计划-整合领料配置到平台配置清单.md new file mode 100644 index 0000000..99f9a27 --- /dev/null +++ b/reference_docs/重构计划-整合领料配置到平台配置清单.md @@ -0,0 +1,650 @@ +# 重构计划:整合 [领料配置] 到 [平台配置清单] + +## 一、背景概述 + +### 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