refactor: integrate [领料配置] into [平台配置清单] and add category selection conditions

Major changes:
- Remove dependency on separate [领料配置] sheet
- Load all data from [平台配置清单] in a single pass
- Add "类别选用条件" (CategorySelectCondition) field to support dynamic category filtering
- Add "66代码" (Code66) field for 66-system integration
- Refactor completeness check logic to only verify required categories

Data model changes:
- clsMaterialItem: Add CategorySelectCondition and Code66 properties
- clsCategory: Add CategorySelectCondition property and IsRequiredForModel() method
- clsBOMManager: Refactor LoadData() to use single data source
- clsBOMManager: Add GetRequiredCategories() method
- clsBOMManager: Update GetValidMaterialsByModel() to use category selection conditions

New workflow:
- LoadData() now takes only wsPlatform parameter (removed wsConfig)
- Categories are filtered by "类别选用条件" before completeness check
- Categories with empty selection condition are always required
- Categories with non-empty condition are checked against model specifications

Documentation:
- Update CLAUDE.md with new sheet structure
- Add comprehensive refactoring plan document
- Add v2.0 LoadData flow analysis with Mermaid diagrams
- Add platform configuration sheet reference

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-01-29 10:25:28 +08:00
parent fd414f7673
commit a9b3188d23
7 changed files with 1419 additions and 88 deletions

View File

@@ -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[读取物料基础信息<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

@@ -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 |

View File

@@ -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