# 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
键: 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 ├── "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 行数据
code, catName, parentCatName] S2_Read --> S2_CheckCode{code 是否为空?} S2_CheckCode -->|是| S2_NextRow[跳到下一行] S2_CheckCode -->|否| S2_CheckCat{类别是否已存在?} S2_CheckCat -->|否| S2_CreateCat[创建新的 clsCategory 对象
设置 categoryName 和 ParentCategoryName
添加到 dictCategories] S2_CheckCat -->|是| S2_Exists[类别已存在] S2_CreateCat --> S2_CheckMat{物料在
dictAllMaterials?} S2_Exists --> S2_CheckMat S2_CheckMat -->|是| S2_UpdateMat[更新物料信息:
mat.Category = catName
mat.ParentCategory = parentCatName
添加到类别 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 ├── "表壳" → 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
所有键} S3_Loop -->|下一个键| S3_GetCat[获取类别对象 cat] S3_GetCat --> S3_CheckParent{ParentCategoryName
是否为空?} S3_CheckParent -->|否, 有父类别| S3_ParentExists{父类别
存在?} S3_CheckParent -->|是, 无父类别| S3_AddRoot[添加到 rootCategories
作为根类别] S3_ParentExists -->|是| S3_AddSub[添加到父类别的
SubCategories 集合] S3_ParentExists -->|否| S3_Orphan[父类别不存在
跳过建立关系] 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 ├── "表壳" (根类别) ├── "部件" (根类别) │ └── 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. **结构构建**:构建类别的树形层级关系 最终形成的数据结构支持: - 快速按代号查找物料 - 快速按类别查找物料 - 遍历完整的类别树 - 支持父类别/子类别的领料逻辑