From 04099d25bc22f3f1e139edad1e34dee0d37a86fd Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Mon, 16 Mar 2026 09:58:29 +0800 Subject: [PATCH] docs: add extraction remarks data source documentation Co-Authored-By: Claude Sonnet 4.6 --- docs/提取备注数据来源.md | 444 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 docs/提取备注数据来源.md diff --git a/docs/提取备注数据来源.md b/docs/提取备注数据来源.md new file mode 100644 index 0000000..1384681 --- /dev/null +++ b/docs/提取备注数据来源.md @@ -0,0 +1,444 @@ +# 提取备注字段数据来源分析 + +**文档生成时间**: 2026-03-13 +**入口函数**: `MainModule.ProcessProductModels()` +**输出位置**: `BOM 提取结果` 工作表的"提取备注"列(最后一列) + +--- + +## 核心数据流图 + +```mermaid +flowchart TD + A[ProcessProductModels
主入口] --> B[ProcessSingleModel
处理单个型号] + + B --> C{解析产品型号
parser.Parse} + + C -->|解析失败 | D["extractNote =
解析失败: + ErrorMessage"] + C -->|解析成功 | E[BomExtractor.ExtractBom
提取 BOM] + + E --> F[DetermineRequiredCategories
确定必需类别] + F --> G[MatchItems
匹配物料] + + G --> H{匹配数量?} + H -->|0 条 | I[不立即报错
移交 ValidateResult] + H -->|1 条 | J[正常添加到结果集] + H -->|多条 | K["记录错误到 pErrorMessages
类别 X 匹配到多条物料 N 条"] + K --> L[设置 item.MatchError
并添加所有匹配项] + + I --> M[ValidateResult
双向覆盖检查] + L --> M + J --> M + + M --> N{必需类别存在?} + N -->|被子类覆盖 | O[视为正常
不报错] + N -->|被父类覆盖 | O + N -->|确实缺失 | P["记录错误
必需类别 X 未匹配"] + + O --> Q[GetErrorSummary
汇总错误] + 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 =
未匹配到任何物料"] + V -->|否 | Y[保持现有 extractNote] + + X --> Z1[CreateOutputRowArray
创建输出行] + Y --> Z1 + + W --> AA[For Each item
itemNote = extractNote] + AA --> AB{item.MatchError
非空?} + AB -->|是 | AC["拼接:itemNote +=
; + MatchError"] + AB -->|否 | AD[保持 itemNote] + + AC --> AE[CreateOutputRowArray
创建输出行] + AD --> AE + + Z1 --> AF[输出到 BOM 提取结果
提取备注列] + 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[被父类覆盖
不报错] + C -->|否 | E[检查 2] + B -->|否 | E + + E --> F{检查 2: 有子类?} + F -->|是 | G{所有子类都匹配?} + G -->|是 | H[被子类覆盖
不报错] + G -->|否 | I[确实缺失
报错] + 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. "未匹配到任何物料"仅在无其他错误时显示 + +--- + +**文档结束**