445 lines
13 KiB
Markdown
445 lines
13 KiB
Markdown
# 提取备注字段数据来源分析
|
||
|
||
**文档生成时间**: 2026-03-13
|
||
**入口函数**: `MainModule.ProcessProductModels()`
|
||
**输出位置**: `BOM 提取结果` 工作表的"提取备注"列(最后一列)
|
||
|
||
---
|
||
|
||
## 核心数据流图
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[ProcessProductModels<br/>主入口] --> B[ProcessSingleModel<br/>处理单个型号]
|
||
|
||
B --> C{解析产品型号<br/>parser.Parse}
|
||
|
||
C -->|解析失败 | D["extractNote = <br/>解析失败: + ErrorMessage"]
|
||
C -->|解析成功 | E[BomExtractor.ExtractBom<br/>提取 BOM]
|
||
|
||
E --> F[DetermineRequiredCategories<br/>确定必需类别]
|
||
F --> G[MatchItems<br/>匹配物料]
|
||
|
||
G --> H{匹配数量?}
|
||
H -->|0 条 | I[不立即报错<br/>移交 ValidateResult]
|
||
H -->|1 条 | J[正常添加到结果集]
|
||
H -->|多条 | K["记录错误到 pErrorMessages<br/>类别 X 匹配到多条物料 N 条"]
|
||
K --> L[设置 item.MatchError<br/>并添加所有匹配项]
|
||
|
||
I --> M[ValidateResult<br/>双向覆盖检查]
|
||
L --> M
|
||
J --> M
|
||
|
||
M --> N{必需类别存在?}
|
||
N -->|被子类覆盖 | O[视为正常<br/>不报错]
|
||
N -->|被父类覆盖 | O
|
||
N -->|确实缺失 | P["记录错误<br/>必需类别 X 未匹配"]
|
||
|
||
O --> Q[GetErrorSummary<br/>汇总错误]
|
||
P --> Q
|
||
|
||
Q --> R{bomErrors 为空?}
|
||
R -->|非空 | S[extractNote = bomErrors]
|
||
R -->|空 | T[extractNote 保持空]
|
||
|
||
S --> U{matchedItems 为空?}
|
||
T --> U
|
||
|
||
U -->|是 | V{extractNote 为空?}
|
||
U -->|否 | W[遍历每个 item]
|
||
|
||
V -->|是 | X["extractNote = <br/>未匹配到任何物料"]
|
||
V -->|否 | Y[保持现有 extractNote]
|
||
|
||
X --> Z1[CreateOutputRowArray<br/>创建输出行]
|
||
Y --> Z1
|
||
|
||
W --> AA[For Each item<br/>itemNote = extractNote]
|
||
AA --> AB{item.MatchError<br/>非空?}
|
||
AB -->|是 | AC["拼接:itemNote += <br/>; + MatchError"]
|
||
AB -->|否 | AD[保持 itemNote]
|
||
|
||
AC --> AE[CreateOutputRowArray<br/>创建输出行]
|
||
AD --> AE
|
||
|
||
Z1 --> AF[输出到 BOM 提取结果<br/>提取备注列]
|
||
AE --> AF
|
||
|
||
style D fill:#ff6b6b
|
||
style K fill:#ffa94d
|
||
style P fill:#ff6b6b
|
||
style X fill:#51cf66
|
||
style AF fill:#339af0,color:#fff
|
||
```
|
||
|
||
---
|
||
|
||
## 详细数据来源
|
||
|
||
### 来源 1: 产品型号解析失败
|
||
|
||
**位置**: `MainModule.bas` 第 184-188 行
|
||
**触发条件**: `ProductModelParser.Parse()` 返回 `False`
|
||
**错误来源**: `ProductModelParser.ErrorMessage`
|
||
|
||
```vba
|
||
' MainModule.bas:184-188
|
||
If Not parser.Parse(modelString) Then
|
||
' 解析失败
|
||
extractNote = "解析失败:" & parser.ErrorMessage
|
||
outputData.Add CreateOutputRowArray(..., extractNote, Nothing)
|
||
Exit Sub
|
||
End If
|
||
```
|
||
|
||
**可能的错误消息**(来自 `ProductModelParser.cls`):
|
||
|
||
| 错误场景 | 错误消息示例 | 源码位置 |
|
||
|---------|-------------|---------|
|
||
| 缺少表头部分 | `型号格式错误:缺少表头部分` | Parse() L73 |
|
||
| 缺少'-'分隔符 | `表头格式错误:缺少'-'分隔符` | ParseHeader() L108 |
|
||
| 表头结构不完整 | `表头结构不完整:缺少必要字段` | ParseHeader() L114 |
|
||
| 过程连接代码格式错误 | `过程连接代码格式错误:长度不足` | ExtractConnectionAndMaterial() L205 |
|
||
| 最后一位不是数字 | `过程连接代码格式错误:最后一位不是数字` | ExtractConnectionAndMaterial() L213 |
|
||
| 解析异常 | `解析表头异常:[VBA 错误描述]` | ParseHeader() ErrorHandler L131 |
|
||
|
||
---
|
||
|
||
### 来源 2: BOM 提取器错误汇总
|
||
|
||
**位置**: `MainModule.bas` 第 197-200 行
|
||
**触发条件**: `BomExtractor.GetErrorSummary()` 返回非空字符串
|
||
**错误来源**: `BomExtractor.pErrorMessages` 集合
|
||
|
||
```vba
|
||
' MainModule.bas:197-200
|
||
Dim bomErrors As String
|
||
bomErrors = BomExtractor.GetErrorSummary
|
||
If bomErrors <> "" Then
|
||
extractNote = bomErrors
|
||
End If
|
||
```
|
||
|
||
**错误汇总逻辑**(`BomExtractor.cls` L471-481):
|
||
|
||
```vba
|
||
Public Function GetErrorSummary() As String
|
||
If pErrorMessages.Count = 0 Then
|
||
GetErrorSummary = ""
|
||
Else
|
||
Dim result As String
|
||
Dim msg As Variant
|
||
For Each msg In pErrorMessages
|
||
result = result & CStr(msg) & "; " ' 使用"; " 连接
|
||
Next msg
|
||
GetErrorSummary = result
|
||
End If
|
||
End Function
|
||
```
|
||
|
||
---
|
||
|
||
### 来源 3: 多匹配错误
|
||
|
||
**位置**: `BomExtractor.cls` 第 247-258 行
|
||
**触发条件**: 同一类别匹配到多条物料
|
||
**错误消息**: `类别 [X] 匹配到多条物料 (N 条)`
|
||
|
||
```vba
|
||
' BomExtractor.cls:247-258
|
||
ElseIf categoryMatches.Count = 1 Then
|
||
' 正常:匹配到 1 条
|
||
pMatchedItems.Add categoryMatches(1)
|
||
Else
|
||
' 异常:匹配到多条
|
||
Dim multiMsg As String
|
||
multiMsg = "类别 [" & category & "] 匹配到多条物料 (" & categoryMatches.Count & "条)"
|
||
pErrorMessages.Add multiMsg
|
||
|
||
' 临时处理:输出所有匹配的
|
||
Dim tempItem As BomItem
|
||
For Each tempItem In categoryMatches
|
||
tempItem.MatchError = multiMsg ' ← 设置到 item
|
||
pMatchedItems.Add tempItem
|
||
Next tempItem
|
||
End If
|
||
```
|
||
|
||
**特性**:
|
||
- 错误同时添加到 `pErrorMessages`(进入 GetErrorSummary)
|
||
- 同时设置到 `item.MatchError`(逐行附加)
|
||
- 输出所有匹配项,但每条都带警告
|
||
|
||
---
|
||
|
||
### 来源 4: 必需类别缺失
|
||
|
||
**位置**: `BomExtractor.cls` 第 449-451 行
|
||
**触发条件**: ValidateResult 检测到必需类别未匹配且无覆盖
|
||
**错误消息**: `必需类别 [X] 未匹配`
|
||
|
||
```vba
|
||
' BomExtractor.cls:449-451
|
||
If Not isResolved Then
|
||
pErrorMessages.Add "必需类别 [" & category & "] 未匹配"
|
||
End If
|
||
```
|
||
|
||
**双向覆盖检查逻辑**:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[必需类别 X 缺失] --> B{检查 1: 有父类?}
|
||
B -->|是 | C{父类别已匹配?}
|
||
C -->|是 | D[被父类覆盖<br/>不报错]
|
||
C -->|否 | E[检查 2]
|
||
B -->|否 | E
|
||
|
||
E --> F{检查 2: 有子类?}
|
||
F -->|是 | G{所有子类都匹配?}
|
||
G -->|是 | H[被子类覆盖<br/>不报错]
|
||
G -->|否 | I[确实缺失<br/>报错]
|
||
F -->|否 | I
|
||
|
||
D --> J[Continue]
|
||
H --> J
|
||
I --> K[添加到 pErrorMessages]
|
||
|
||
style D fill:#51cf66
|
||
style H fill:#51cf66
|
||
style K fill:#ff6b6b
|
||
```
|
||
|
||
**覆盖场景示例**:
|
||
|
||
| 场景 | 父类别 | 子类别 1 | 子类别 2 | 结果 |
|
||
|------|--------|---------|---------|------|
|
||
| 总成优先 | ✅ 部件 (1 条) | ✅ 接头 (2 条) | ✅ 弹性元件 (1 条) | 输出"部件",子类不报错 |
|
||
| 散件满足 | ❌ 部件 (缺失) | ✅ 接头 (2 条) | ✅ 弹性元件 (1 条) | 输出子类,父类不报错 |
|
||
| 确实缺失 | ❌ 部件 (缺失) | ❌ 接头 (缺失) | ✅ 弹性元件 (1 条) | 报错:"必需类别 [接头] 未匹配" |
|
||
|
||
---
|
||
|
||
### 来源 5: 无匹配物料
|
||
|
||
**位置**: `MainModule.bas` 第 203-208 行
|
||
**触发条件**: `matchedItems.Count = 0` 且 `extractNote` 为空
|
||
**错误消息**: `未匹配到任何物料`
|
||
|
||
```vba
|
||
' MainModule.bas:203-208
|
||
If matchedItems.Count = 0 Then
|
||
' 没有匹配项
|
||
If extractNote = "" Then
|
||
extractNote = "未匹配到任何物料"
|
||
End If
|
||
outputData.Add CreateOutputRowArray(..., extractNote, Nothing)
|
||
```
|
||
|
||
**注意**: 如果已有其他错误(如解析错误),则不会覆盖。
|
||
|
||
---
|
||
|
||
### 来源 6: 物料级 MatchError
|
||
|
||
**位置**: `MainModule.bas` 第 215-223 行
|
||
**触发条件**: `BomItem.MatchError` 非空
|
||
**错误来源**: 由 `BomExtractor.MatchItems()` 设置(见来源 3)
|
||
|
||
```vba
|
||
' MainModule.bas:215-223
|
||
For Each item In matchedItems
|
||
Dim itemNote As String
|
||
itemNote = extractNote
|
||
|
||
' 添加物料特定的错误
|
||
If item.MatchError <> "" Then
|
||
If itemNote <> "" Then itemNote = itemNote & "; "
|
||
itemNote = itemNote & item.MatchError
|
||
End If
|
||
|
||
' ... 添加到输出
|
||
Next item
|
||
```
|
||
|
||
**特性**:
|
||
- 每条 BOM 物料单独输出时附加
|
||
- 使用 `"; "` 分隔符拼接
|
||
- 首行继承全局 `extractNote`,后续行只继承不含物料级错误
|
||
|
||
---
|
||
|
||
## 错误消息拼接规则
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant M as MainModule
|
||
participant P as ProductModelParser
|
||
participant E as BomExtractor
|
||
participant I as BomItem
|
||
|
||
M->>P: Parse modelString
|
||
alt 解析失败
|
||
P-->>M: 返回 ErrorMessage
|
||
Note over M: extractNote = 解析失败 + ErrorMessage
|
||
M->>M: 输出单行后结束
|
||
else 解析成功
|
||
M->>E: ExtractBom conditions
|
||
Note over E: 收集错误到 pErrorMessages
|
||
Note over E: 设置 item.MatchError
|
||
E-->>M: GetErrorSummary
|
||
|
||
alt bomErrors 非空
|
||
Note over M: extractNote = bomErrors
|
||
else bomErrors 为空
|
||
Note over M: extractNote 保持空
|
||
end
|
||
|
||
alt matchedItems = 0
|
||
Note over M: 设为 未匹配到任何物料
|
||
M->>M: 输出单行后结束
|
||
else matchedItems > 0
|
||
loop For Each item
|
||
M->>M: itemNote = extractNote
|
||
alt item.MatchError 非空
|
||
Note over M: itemNote += MatchError
|
||
end
|
||
M->>M: CreateOutputRowArray
|
||
end
|
||
end
|
||
end
|
||
|
||
Note over M: 输出到提取备注列
|
||
```
|
||
|
||
**拼接规则总结**:
|
||
|
||
| 场景 | 拼接方式 | 示例 |
|
||
|------|---------|------|
|
||
| 全局错误汇总 | 分号连接 | 错误 1; 错误 2 |
|
||
| 物料级附加 | 检查非空后加分号 | extractNote + MatchError |
|
||
| 多类别缺失 | 逐个添加到集合后汇总 | 必需类别 A 未匹配; 必需类别 B 未匹配 |
|
||
| 解析失败 + 其他 | 解析失败时立即退出不叠加 | 解析失败:... |
|
||
|
||
---
|
||
|
||
## 输出列定义
|
||
|
||
**位置**: `MainModule.bas` 第 270 行
|
||
|
||
```vba
|
||
' MainModule.bas:242-270
|
||
Private Sub WriteOutputHeader(ws As Worksheet)
|
||
' ... 前面的列 ...
|
||
ws.Cells(1, col).Value = "66 代码": col = col + 1
|
||
ws.Cells(1, col).Value = "提取备注": col = col + 1 ' ← 最后一列
|
||
End Sub
|
||
```
|
||
|
||
**数据写入**: `CreateOutputRowArray()` 函数的最后一个元素
|
||
|
||
```vba
|
||
' MainModule.bas:283-339
|
||
Private Function CreateOutputRowArray(..., note As String, ...) As Variant()
|
||
' ... 填充前面的列 ...
|
||
|
||
' 备注(最后一列)
|
||
rowData(col) = note
|
||
|
||
CreateOutputRowArray = rowData
|
||
End Function
|
||
```
|
||
|
||
---
|
||
|
||
## 完整错误消息类型汇总表
|
||
|
||
| 错误类型 | 错误消息模板 | 触发条件 | 源码位置 | 是否可叠加 |
|
||
|---------|-------------|---------|---------|----------|
|
||
| **解析错误** | `解析失败:[具体原因]` | Parse() 失败 | MainModule.bas:186 | ❌ 单独输出 |
|
||
| **多匹配** | `类别 [X] 匹配到多条物料 (N 条)` | 同类别匹配>1 | BomExtractor.cls:250 | ✅ 可叠加 |
|
||
| **必需类别缺失** | `必需类别 [X] 未匹配` | ValidateResult 检测缺失 | BomExtractor.cls:450 | ✅ 可叠加 |
|
||
| **无匹配** | `未匹配到任何物料` | matchedItems.Count = 0 | MainModule.bas:207 | ❌ 仅当无其他错误 |
|
||
| **物料级多匹配** | `类别 [X] 匹配到多条物料 (N 条)` | item.MatchError | MainModule.bas:221 | ✅ 逐行附加 |
|
||
|
||
---
|
||
|
||
## 典型输出示例
|
||
|
||
### 示例 1: 解析失败
|
||
```
|
||
产品型号:Y-100-M203.316SS
|
||
提取备注:解析失败:表头结构不完整:缺少必要字段
|
||
```
|
||
|
||
### 示例 2: 正常匹配(无错误)
|
||
```
|
||
产品型号:Y-100-M203.316SS.L100.N2
|
||
提取备注:(空)
|
||
```
|
||
|
||
### 示例 3: 多匹配错误
|
||
```
|
||
产品型号:Y-100-M203.316SS.L100.N2
|
||
提取备注:类别 [接液材质] 匹配到多条物料 (3 条); 类别 [量程范围] 匹配到多条物料 (2 条);
|
||
```
|
||
|
||
### 示例 4: 必需类别缺失
|
||
```
|
||
产品型号:Y-100-M203.316SS.L100.N2
|
||
提取备注:必需类别 [安装形式] 未匹配; 必需类别 [表壳形式] 未匹配;
|
||
```
|
||
|
||
### 示例 5: 无匹配
|
||
```
|
||
产品型号:INVALID-MODEL
|
||
提取备注:未匹配到任何物料
|
||
```
|
||
|
||
### 示例 6: 物料级错误附加
|
||
```
|
||
产品型号:Y-100-M203.316SS.L100.N2
|
||
行号 | 类别 | 提取备注
|
||
-----|------|----------
|
||
1 | 接头 | 类别 [接液材质] 匹配到多条物料 (3 条); 类别 [接液材质] 匹配到多条物料 (3 条);
|
||
2 | 弹性元件 | 类别 [接液材质] 匹配到多条物料 (3 条);
|
||
```
|
||
|
||
---
|
||
|
||
## 关键代码路径索引
|
||
|
||
| 功能 | 文件 | 行号范围 |
|
||
|------|------|---------|
|
||
| 主入口 | MainModule.bas | 20-150 |
|
||
| 单型号处理 | MainModule.bas | 161-235 |
|
||
| 输出表头定义 | MainModule.bas | 242-271 |
|
||
| 行数据创建 | MainModule.bas | 283-339 |
|
||
| 型号解析 | ProductModelParser.cls | 60-91 |
|
||
| BOM 提取 | BomExtractor.cls | 130-152 |
|
||
| 匹配物料 | BomExtractor.cls | 208-261 |
|
||
| 总成逻辑 | BomExtractor.cls | 270-367 |
|
||
| 结果验证 | BomExtractor.cls | 376-455 |
|
||
| 错误汇总 | BomExtractor.cls | 471-482 |
|
||
| 物料数据模型 | BomItem.cls | 1-89 |
|
||
|
||
---
|
||
|
||
## 设计特点
|
||
|
||
✅ **优点**:
|
||
1. 错误信息分层清晰(解析层、匹配层、验证层)
|
||
2. 支持多错误叠加,不丢失任何警告
|
||
3. 双向覆盖检查避免误报(总成/散件场景)
|
||
4. 物料级错误逐行附加,便于定位问题
|
||
|
||
⚠️ **注意事项**:
|
||
1. 解析失败时立即退出,不执行后续 BOM 提取
|
||
2. 多匹配错误会输出所有匹配项(数据不确定时保留全部)
|
||
3. 错误消息使用 `"; "` 分隔,末尾可能有多余分隔符
|
||
4. "未匹配到任何物料"仅在无其他错误时显示
|
||
|
||
---
|
||
|
||
**文档结束**
|