diff --git a/VBA_BOMConverter/ClassModules/clsErrorLogger.cls b/VBA_BOMConverter/ClassModules/clsErrorLogger.cls
index 3d50892..5312e4c 100644
--- a/VBA_BOMConverter/ClassModules/clsErrorLogger.cls
+++ b/VBA_BOMConverter/ClassModules/clsErrorLogger.cls
@@ -5,9 +5,11 @@
Option Explicit
Private pErrors As Collection
+Private pWarnings As Collection
Private Sub Class_Initialize()
Set pErrors = New Collection
+ Set pWarnings = New Collection
End Sub
' 记录错误
@@ -21,35 +23,83 @@ Public Property Get HasErrors() As Boolean
HasErrors = (pErrors.count > 0)
End Property
+' 是否有警告
+Public Property Get HasWarnings() As Boolean
+ HasWarnings = (pWarnings.count > 0)
+End Property
+
+' 是否有问题(错误或警告)
+Public Property Get HasIssues() As Boolean
+ HasIssues = (pErrors.count > 0 Or pWarnings.count > 0)
+End Property
+
+' 记录警告
+Public Sub RecordWarning(RowIndex As Long, SourceFunc As String, WarningType As String, Desc As String, Context As String)
+ pWarnings.Add Array(RowIndex, SourceFunc, WarningType, Desc, Context)
+End Sub
+
' 输出报告到新工作表
Public Sub PrintReport(targetWb As Workbook)
- If pErrors.count = 0 Then Exit Sub
+ If pErrors.count = 0 And pWarnings.count = 0 Then Exit Sub
Dim ws As Worksheet
Set ws = targetWb.Worksheets.Add(After:=targetWb.Worksheets(targetWb.Worksheets.count))
ws.Name = "错误报告_" & Format(Now, "hhmmss")
- ' 表头
- ws.Range("A1:E1").Value = Array("原表行号", "来源模块", "错误类型", "详细描述", "原始数据")
- ws.Range("A1:E1").Font.Bold = True
- ws.Range("A1:E1").Interior.Color = RGB(255, 200, 200)
+ ' 表头 (增加"类型"列)
+ ws.Range("A1:F1").Value = Array("类型", "原表行号", "来源模块", "错误类型", "详细描述", "原始数据")
+ ws.Range("A1:F1").Font.Bold = True
+ ws.Range("A1:F1").Interior.Color = RGB(217, 217, 217)
' 准备输出数组
+ Dim totalIssues As Long
+ totalIssues = pErrors.count + pWarnings.count
+
Dim arrOutput() As Variant
- ReDim arrOutput(1 To pErrors.count, 1 To 5)
+ ReDim arrOutput(1 To totalIssues, 1 To 6)
Dim i As Long
Dim vItem As Variant
+ ' 先输出错误
For i = 1 To pErrors.count
vItem = pErrors(i)
- arrOutput(i, 1) = vItem(0)
- arrOutput(i, 2) = vItem(1)
- arrOutput(i, 3) = vItem(2)
- arrOutput(i, 4) = vItem(3)
- arrOutput(i, 5) = vItem(4)
+ arrOutput(i, 1) = "错误"
+ arrOutput(i, 2) = vItem(0)
+ arrOutput(i, 3) = vItem(1)
+ arrOutput(i, 4) = vItem(2)
+ arrOutput(i, 5) = vItem(3)
+ arrOutput(i, 6) = vItem(4)
Next i
- ws.Range("A2").Resize(UBound(arrOutput, 1), 5).Value = arrOutput
+ ' 再输出警告
+ For i = 1 To pWarnings.count
+ vItem = pWarnings(i)
+ arrOutput(pErrors.count + i, 1) = "警告"
+ arrOutput(pErrors.count + i, 2) = vItem(0)
+ arrOutput(pErrors.count + i, 3) = vItem(1)
+ arrOutput(pErrors.count + i, 4) = vItem(2)
+ arrOutput(pErrors.count + i, 5) = vItem(3)
+ arrOutput(pErrors.count + i, 6) = vItem(4)
+ Next i
+
+ ws.Range("A2").Resize(totalIssues, 6).Value = arrOutput
+
+ ' 标记错误和警告行
+ If totalIssues > 0 Then
+ Dim rngErrors As Range
+ Dim rngWarnings As Range
+
+ If pErrors.count > 0 Then
+ Set rngErrors = ws.Range("A2").Resize(pErrors.count, 6)
+ rngErrors.Interior.Color = RGB(255, 200, 200)
+ End If
+
+ If pWarnings.count > 0 Then
+ Set rngWarnings = ws.Range("A" & (pErrors.count + 2)).Resize(pWarnings.count, 6)
+ rngWarnings.Interior.Color = RGB(255, 255, 200)
+ End If
+ End If
+
ws.Columns.AutoFit
End Sub
\ No newline at end of file
diff --git a/VBA_BOMConverter/Modules/M09_BOMExtractor.bas b/VBA_BOMConverter/Modules/M09_BOMExtractor.bas
index 428789c..4602ca8 100644
--- a/VBA_BOMConverter/Modules/M09_BOMExtractor.bas
+++ b/VBA_BOMConverter/Modules/M09_BOMExtractor.bas
@@ -132,7 +132,7 @@ Public Function RunBOMExtraction() As String
Set wsOutput = WriteExtractionResults(allResults)
' 步骤6: 生成错误报告
- If g_Logger.HasErrors Then
+ If g_Logger.HasIssues Then
g_Logger.PrintReport ActiveWorkbook
End If
@@ -165,6 +165,8 @@ Public Function RunBOMExtraction() As String
If g_Logger.HasErrors Then
msg = msg & vbCrLf & vbCrLf & "发现错误,已生成错误报告工作表。"
+ ElseIf g_Logger.HasWarnings Then
+ msg = msg & vbCrLf & vbCrLf & "发现警告,已生成错误报告工作表。"
End If
RunBOMExtraction = msg
@@ -297,8 +299,8 @@ End Function
'
' 流程:
' 1. 解析型号,提取参数
-' 2. 匹配所有物料类型
-' 3. 验证部件组合
+' 2. 匹配所有物料类型(两阶段:收集 → 验证)
+' 3. 验证所有匹配结果
' 4. 生成输出行
' ------------------------------------------------------------------------------
Private Function ProcessSingleModel( _
@@ -323,15 +325,12 @@ Private Function ProcessSingleModel( _
Exit Function
End If
- ' 步骤2: 匹配所有物料类型
+ ' 步骤2: 匹配所有物料类型(两阶段)
Dim allMaterials As Collection
- Set allMaterials = MatchAllMaterialTypes(params, bomWb, logger)
-
- ' 步骤3: 验证部件组合
Dim validation As Object
- Set validation = M08_ComponentProcessor.ValidateComponentCombination(allMaterials)
+ Set allMaterials = MatchAllMaterialTypesWithValidation(params, bomWb, logger, validation)
- ' 步骤4: 生成输出行
+ ' 步骤3: 生成输出行
Dim remarks As String
remarks = ""
@@ -365,33 +364,40 @@ ErrorHandler:
End Function
' ------------------------------------------------------------------------------
-' 匹配所有物料类型
+' 匹配所有物料类型(两阶段:收集 → 验证)
'
' 输入:
' params - 参数字典
' bomWb - BOM库工作簿
' logger - 错误记录器
+' validation - 输出参数,返回验证结果
'
' 输出:
' Collection - 所有匹配到的物料集合
'
' 逻辑:
-' - 遍历BOM库中的所有工作表
-' - 对每个工作表尝试匹配
+' Phase 1: 收集阶段 - 遍历BOM库中的所有工作表,收集匹配结果(不记录错误)
+' Phase 2: 验证阶段 - 统一验证所有结果,记录错误和警告
+' Phase 3: 生成最终物料集合
+'
+' 特殊处理:
' - "部件"工作表特殊处理(调用M08_ComponentProcessor)
-' - 其他工作表使用M07_BOMMatcher匹配
+' - 部件、接头、弹性元件的交叉验证
' ------------------------------------------------------------------------------
-Private Function MatchAllMaterialTypes( _
+Private Function MatchAllMaterialTypesWithValidation( _
ByVal params As Object, _
ByVal bomWb As Workbook, _
- ByVal logger As clsErrorLogger _
+ ByVal logger As clsErrorLogger, _
+ ByRef outValidation As Object _
) As Collection
On Error GoTo ErrorHandler
- Dim allMaterials As Collection
- Set allMaterials = New Collection
+ ' ========================================
+ ' Phase 1: 收集所有工作表的匹配结果
+ ' ========================================
+ Dim resultsDict As Object
+ Set resultsDict = CreateObject("Scripting.Dictionary")
- ' 遍历BOM库中的所有工作表
Dim ws As Worksheet
For Each ws In bomWb.Worksheets
Dim sheetName As String
@@ -399,6 +405,15 @@ Private Function MatchAllMaterialTypes( _
Debug.Print "=== 处理工作表: [" & sheetName & "] ==="
+ ' 创建匹配结果对象
+ Dim matchResult As Object
+ Set matchResult = CreateObject("Scripting.Dictionary")
+ matchResult("sheetName") = sheetName
+ matchResult("success") = False
+ matchResult("rowCount") = 0
+ Set matchResult("rowNums") = New Collection
+ Set matchResult("materials") = New Collection
+
' "部件"工作表特殊处理
If sheetName = BOMLIB_SHEET_COMPONENT Then
Debug.Print " -> 使用部件处理逻辑"
@@ -409,62 +424,340 @@ Private Function MatchAllMaterialTypes( _
Debug.Print " -> 返回物料数: " & componentMaterials.count
+ matchResult("success") = (componentMaterials.count > 0)
+ matchResult("rowCount") = componentMaterials.count
+
Dim compMat As Variant
For Each compMat In componentMaterials
Debug.Print " [" & compMat("materialType") & "] 名称=[" & compMat("materialName") & "] 编码=[" & compMat("materialCode") & "]"
- allMaterials.Add compMat
+ matchResult("materials").Add compMat
Next compMat
Else
' 其他工作表使用标准匹配逻辑
- ' 匹配记录
- Dim matchResult As Object
- Set matchResult = M07_BOMMatcher.MatchBOMRecord(ws, params)
+ Dim bomMatchResult As Object
+ Set bomMatchResult = M07_BOMMatcher.MatchBOMRecord(ws, params)
- Debug.Print " -> 匹配结果: " & matchResult("success") & ", 行数: " & matchResult("rowCount")
+ Debug.Print " -> 匹配结果: " & bomMatchResult("success") & ", 行数: " & bomMatchResult("rowCount")
+
+ matchResult("success") = bomMatchResult("success")
+ matchResult("rowCount") = bomMatchResult("rowCount")
+ Set matchResult("rowNums") = bomMatchResult("rowNums")
' 如果匹配成功,提取物料信息
- If matchResult("success") Then
+ If bomMatchResult("success") Then
Dim rowNum As Long
- rowNum = matchResult("rowNums")(1)
+ rowNum = bomMatchResult("rowNums")(1)
Debug.Print " -> 匹配行号: " & rowNum
Dim headerMap As Object
Set headerMap = M07_BOMMatcher.BuildWorksheetHeaderMap(ws)
- Debug.Print " -> 表头映射键数: " & headerMap.count
-
Dim materialInfo As Object
Set materialInfo = M07_BOMMatcher.ExtractMaterialInfo(ws, rowNum, headerMap)
Debug.Print " -> 提取结果: 名称=[" & materialInfo("materialName") & "] 编码=[" & materialInfo("materialCode") & "] 数量=[" & materialInfo("materialQty") & "]"
If Not materialInfo Is Nothing Then
- allMaterials.Add materialInfo
- Debug.Print " -> 已添加到物料集合"
+ matchResult("materials").Add materialInfo
+ Debug.Print " -> 已添加到匹配结果"
Else
Debug.Print " -> ERROR: materialInfo为Nothing"
End If
- Else
- ' 匹配失败,记录错误(但不中断处理)
- If matchResult("rowCount") = 0 Then
- logger.Record 0, "M09.MatchAllMaterialTypes", "BOMMatchError", _
- sheetName & " " & matchResult("message"), ""
- Debug.Print " -> 未匹配到记录"
- End If
End If
End If
+
+ ' 将匹配结果存入字典
+ resultsDict.Add sheetName, matchResult
Debug.Print ""
Next ws
- Set MatchAllMaterialTypes = allMaterials
+ ' ========================================
+ ' Phase 2: 统一验证所有匹配结果
+ ' ========================================
+ Dim validationResult As Object
+ Set validationResult = ValidateAllMatchResults(resultsDict, logger)
+
+ ' ========================================
+ ' Phase 3: 根据验证结果生成最终物料集合
+ ' ========================================
+ Dim allMaterials As Collection
+ Set allMaterials = New Collection
+
+ If validationResult("valid") Then
+ ' 验证通过,收集所有物料
+ Dim resultKey As Variant
+ For Each resultKey In resultsDict.Keys
+ Dim result As Object
+ Set result = resultsDict(resultKey)
+
+ Dim mat As Variant
+ For Each mat In result("materials")
+ allMaterials.Add mat
+ Next mat
+ Next resultKey
+ Else
+ ' 验证失败,仍然收集物料以便在输出中显示错误
+ Dim resultKey2 As Variant
+ For Each resultKey2 In resultsDict.Keys
+ Dim result2 As Object
+ Set result2 = resultsDict(resultKey2)
+
+ Dim mat2 As Variant
+ For Each mat2 In result2("materials")
+ allMaterials.Add mat2
+ Next mat2
+ Next resultKey2
+ End If
+
+ ' 设置输出参数
+ Set outValidation = validationResult
+
+ Set MatchAllMaterialTypesWithValidation = allMaterials
Exit Function
ErrorHandler:
If Not logger Is Nothing Then
- logger.Record 0, "M09.MatchAllMaterialTypes", "SystemError", _
+ logger.Record 0, "M09.MatchAllMaterialTypesWithValidation", "SystemError", _
"匹配物料类型失败: " & Err.Description, ""
End If
- Set MatchAllMaterialTypes = allMaterials
+
+ Dim emptyColl As Collection
+ Set emptyColl = New Collection
+ Set MatchAllMaterialTypesWithValidation = emptyColl
+End Function
+
+' ------------------------------------------------------------------------------
+' 统一验证所有工作表的匹配结果
+'
+' 输入:
+' resultsDict - 所有工作表的匹配结果字典
+' logger - 错误记录器
+'
+' 输出:
+' Object - 验证结果对象
+' .valid - Boolean,验证是否通过
+' .message - String,错误/警告消息
+'
+' 验证规则:
+' 1. 基础规则(所有工作表):
+' - 恰好匹配1条记录 → 正常
+' - 匹配0条记录 → 错误(部件/接头/弹性元件除外)
+' - 匹配2+条记录 → 错误
+'
+' 2. 特殊规则(部件、接头、弹性元件):
+' - 互斥关系验证
+' - 组合完整性验证
+' - 警告处理
+' ------------------------------------------------------------------------------
+Private Function ValidateAllMatchResults( _
+ ByVal resultsDict As Object, _
+ ByVal logger As clsErrorLogger _
+) As Object
+ On Error GoTo ErrorHandler
+
+ Dim validation As Object
+ Set validation = CreateObject("Scripting.Dictionary")
+ validation("valid") = True
+ validation("message") = ""
+
+ ' ========================================
+ ' 1. 基础验证:非特殊工作表必须恰好1条
+ ' ========================================
+ Dim specialSheets As Variant
+ specialSheets = Array(BOMLIB_SHEET_COMPONENT, "接头", "弹性元件")
+
+ Dim errors As Collection
+ Set errors = New Collection
+
+ Dim warnings As Collection
+ Set warnings = New Collection
+
+ Dim sheetKey As Variant
+ For Each sheetKey In resultsDict.Keys
+ Dim result As Object
+ Set result = resultsDict(sheetKey)
+
+ Dim isSpecialSheet As Boolean
+ isSpecialSheet = False
+
+ Dim s As Variant
+ For Each s In specialSheets
+ If result("sheetName") = s Then
+ isSpecialSheet = True
+ Exit For
+ End If
+ Next s
+
+ If Not isSpecialSheet Then
+ If result("rowCount") = 0 Then
+ errors.Add result("sheetName") & " 未匹配到记录"
+ ElseIf result("rowCount") > 1 Then
+ errors.Add result("sheetName") & " 匹配到" & result("rowCount") & "条记录"
+ End If
+ End If
+ Next sheetKey
+
+ ' ========================================
+ ' 2. 统计特殊工作表的结果
+ ' ========================================
+ Dim componentResult As Object
+ Set componentResult = Nothing
+
+ Dim jointResult As Object
+ Set jointResult = Nothing
+
+ Dim elementResult As Object
+ Set elementResult = Nothing
+
+ If resultsDict.Exists(BOMLIB_SHEET_COMPONENT) Then
+ Set componentResult = resultsDict(BOMLIB_SHEET_COMPONENT)
+ End If
+
+ If resultsDict.Exists("接头") Then
+ Set jointResult = resultsDict("接头")
+ End If
+
+ If resultsDict.Exists("弹性元件") Then
+ Set elementResult = resultsDict("弹性元件")
+ End If
+
+ ' 统计[部件]工作表中返回的物料类型
+ Dim componentFromComponentSheet As Boolean
+ componentFromComponentSheet = False
+
+ Dim componentJointCount As Long
+ componentJointCount = 0
+
+ Dim componentElementCount As Long
+ componentElementCount = 0
+
+ If Not componentResult Is Nothing Then
+ Dim mat As Variant
+ For Each mat In componentResult("materials")
+ If mat("materialType") = BOMLIB_SHEET_COMPONENT Then
+ componentFromComponentSheet = True
+ ElseIf mat("materialType") = "接头" Then
+ componentJointCount = componentJointCount + 1
+ ElseIf mat("materialType") = "弹性元件" Then
+ componentElementCount = componentElementCount + 1
+ End If
+ Next mat
+ End If
+
+ ' ========================================
+ ' 3. 特殊验证:部件、接头、弹性元件
+ ' ========================================
+ Dim jointFromSheet As Long
+ jointFromSheet = 0
+
+ Dim elementFromSheet As Long
+ elementFromSheet = 0
+
+ If Not jointResult Is Nothing Then
+ jointFromSheet = jointResult("rowCount")
+ End If
+
+ If Not elementResult Is Nothing Then
+ elementFromSheet = elementResult("rowCount")
+ End If
+
+ ' 情况A: [部件]工作表返回了部件物料
+ If componentFromComponentSheet Then
+ ' 检查独立工作表是否也匹配到了物料
+ If jointFromSheet > 0 Then
+ warnings.Add "存在[部件]物料,但[接头]工作表也匹配到" & jointFromSheet & "条记录,已忽略"
+ End If
+ If elementFromSheet > 0 Then
+ warnings.Add "存在[部件]物料,但[弹性元件]工作表也匹配到" & elementFromSheet & "条记录,已忽略"
+ End If
+
+ ' 情况B: [部件]工作表返回了子件(接头+弹性元件)
+ ElseIf componentJointCount > 0 Or componentElementCount > 0 Then
+ If jointFromSheet > 0 Then
+ warnings.Add "[部件]工作表已返回接头,但[接头]工作表也匹配到" & jointFromSheet & "条记录,已忽略"
+ End If
+ If elementFromSheet > 0 Then
+ warnings.Add "[部件]工作表已返回弹性元件,但[弹性元件]工作表也匹配到" & elementFromSheet & "条记录,已忽略"
+ End If
+
+ ' 情况C: [部件]工作表没有返回物料,使用独立工作表
+ Else
+ ' 检查[部件]工作表本身是否匹配失败
+ Dim componentCount As Long
+ componentCount = 0
+ If Not componentResult Is Nothing Then
+ componentCount = componentResult("rowCount")
+ End If
+
+ If componentCount = 0 And (jointFromSheet = 0 Or elementFromSheet = 0) Then
+ errors.Add "部件、接头、弹性元件均未匹配或组合不完整"
+ ElseIf jointFromSheet > 1 Then
+ errors.Add "[接头]工作表匹配到" & jointFromSheet & "条记录"
+ ElseIf elementFromSheet > 1 Then
+ errors.Add "[弹性元件]工作表匹配到" & elementFromSheet & "条记录"
+ End If
+ End If
+
+ ' ========================================
+ ' 4. 处理[部件]工作表的多条匹配
+ ' ========================================
+ If Not componentResult Is Nothing Then
+ If componentResult("rowCount") > 1 Then
+ errors.Add "[部件]工作表匹配到" & componentResult("rowCount") & "条记录"
+ End If
+ End If
+
+ ' ========================================
+ ' 5. 记录错误和警告
+ ' ========================================
+ Dim err As Variant
+ For Each err In errors
+ logger.Record 0, "M09.ValidateAllMatchResults", "BOMMatchError", CStr(err), ""
+ Next err
+
+ Dim warn As Variant
+ For Each warn In warnings
+ logger.RecordWarning 0, "M09.ValidateAllMatchResults", "ComponentConflict", CStr(warn), ""
+ Next warn
+
+ ' ========================================
+ ' 6. 生成验证结果
+ ' ========================================
+ If errors.count > 0 Then
+ validation("valid") = False
+ validation("message") = Join(ToArray(errors), "; ")
+ ElseIf warnings.count > 0 Then
+ validation("valid") = True ' 警告不影响验证结果
+ validation("message") = Join(ToArray(warnings), "; ")
+ End If
+
+ Set ValidateAllMatchResults = validation
+ Exit Function
+
+ErrorHandler:
+ If Not logger Is Nothing Then
+ logger.Record 0, "M09.ValidateAllMatchResults", "SystemError", _
+ "验证匹配结果失败: " & Err.Description, ""
+ End If
+
+ validation("valid") = False
+ validation("message") = "验证过程发生系统错误"
+ Set ValidateAllMatchResults = validation
+End Function
+
+' ------------------------------------------------------------------------------
+' 辅助函数:将Collection转换为数组
+' ------------------------------------------------------------------------------
+Private Function ToArray(ByVal coll As Collection) As Variant
+ Dim arr() As Variant
+ ReDim arr(1 To coll.count) As Variant
+
+ Dim i As Long
+ For i = 1 To coll.count
+ arr(i) = coll(i)
+ Next i
+
+ ToArray = arr
End Function
' ------------------------------------------------------------------------------
@@ -648,10 +941,17 @@ Private Function WriteExtractionResults(ByVal results As Collection) As Workshee
.Borders.Weight = xlThin
End With
- ' 如果有错误备注,标红
+ ' 如果有错误备注,标红;如果有警告,标黄
For i = 1 To results.count
- If Len(CStr(outputArr(i, 12))) > 0 And InStr(CStr(outputArr(i, 12)), "错误") > 0 Then
- ws.Cells(i + 1, 12).Interior.Color = RGB(255, 200, 200)
+ Dim remarkText As String
+ remarkText = CStr(outputArr(i, 12))
+
+ If Len(remarkText) > 0 Then
+ If InStr(remarkText, "错误") > 0 Then
+ ws.Cells(i + 1, 12).Interior.Color = RGB(255, 200, 200)
+ ElseIf InStr(remarkText, "警告") > 0 Or InStr(remarkText, "已忽略") > 0 Then
+ ws.Cells(i + 1, 12).Interior.Color = RGB(255, 255, 200)
+ End If
End If
Next i
End If
diff --git a/docs/BOM匹配错误判断机制详解.md b/docs/BOM匹配错误判断机制详解.md
new file mode 100644
index 0000000..72b1dac
--- /dev/null
+++ b/docs/BOM匹配错误判断机制详解.md
@@ -0,0 +1,1297 @@
+# BOM匹配错误判断机制详解
+
+## 目录
+1. [系统概述](#1-系统概述)
+2. [核心匹配算法 (M07_BOMMatcher)](#2-核心匹配算法-m07_bommatcher)
+3. [错误类型与检测](#3-错误类型与检测)
+4. [决策树与流程图](#4-决策树与流程图)
+5. [错误场景实例](#5-错误场景实例)
+6. [部件特殊处理](#6-部件特殊处理)
+7. [错误报告机制](#7-错误报告机制)
+8. [快速参考](#8-快速参考)
+
+---
+
+**重大更新 (v2.0 - 2025-02-12):**
+- ✅ 实施两阶段验证机制(收集 → 验证)
+- ✅ 新增警告类型(非阻断性错误)
+- ✅ 部件/接头/弹性元件交叉验证增强
+- ✅ 错误报告增加"类型"列(错误/警告)
+
+---
+
+## 1. 系统概述
+
+### 1.1 什么是BOM匹配?
+
+BOM匹配是AutoBOM系统的核心功能,它根据产品型号中提取的参数(如`azxs`、`bkxs`、`gclj`、`jycz`、`lcfw`、`fjgn`等),在BOM库中查找**恰好1条**匹配的物料记录。
+
+**核心目标:精确匹配(1:1匹配)**
+
+| 匹配结果 | 状态 | 说明 |
+|---------|------|------|
+| 0条记录 | ❌ 失败 | 未找到匹配物料 |
+| 1条记录 | ✅ 成功 | 唯一匹配,可提取物料信息 |
+| 2+条记录 | ❌ 失败 | 模糊匹配,无法确定唯一物料 |
+
+### 1.2 为什么错误判断至关重要?
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ 错误判断的业务价值 │
+├─────────────────────────────────────────────────────────┤
+│ ✓ 防止错误物料流入生产环节 │
+│ ✓ 避免停工待料(库存不足) │
+│ ✓ 减少人工纠错成本 │
+│ ✓ 保证BOM准确性(可追溯性) │
+│ ✓ 提高生产效率和产品质量 │
+└─────────────────────────────────────────────────────────┘
+```
+
+### 1.3 匹配哲学:精确匹配原则
+
+AutoBOM系统采用**严格的一对一匹配原则**,并采用**两阶段验证机制**:
+
+```mermaid
+graph LR
+ A[产品型号参数] --> B{阶段1: 收集
所有工作表匹配结果}
+ B --> C{阶段2: 统一验证}
+ C -->|0条匹配| D[❌ 失败
记录错误]
+ C -->|1条匹配| E[✅ 成功
提取物料]
+ C -->|2+条匹配| F[❌ 失败
记录错误]
+ C -->|特殊情况| G[⚠️ 警告
记录但不阻断]
+
+ style D fill:#ff6b6b
+ style E fill:#51cf66
+ style F fill:#ff6b6b
+ style G fill:#ffd43b
+```
+
+**两阶段验证的优势:**
+- **阶段1(收集)**: 不立即判断错误,保留所有匹配结果
+- **阶段2(验证)**: 统一分析,支持交叉验证
+- **警告机制**: 非阻断性问题(如部件与子件共存)记录警告但不停止处理
+
+**拒绝"模糊匹配"的原因:**
+- 0条匹配 → 可能遗漏关键参数或BOM库不完整
+- 2+条匹配 → 存在歧义,可能导致发错料
+
+---
+
+## 2. 核心匹配算法 (M07_BOMMatcher)
+
+### 2.1 MatchBOMRecord() 函数详解
+
+`MatchBOMRecord()` 是BOM匹配的主入口函数,位于 `M07_BOMMatcher.bas` 模块。
+
+**函数签名:**
+```vba
+Public Function MatchBOMRecord( _
+ ByVal ws As Worksheet, _
+ ByVal params As Object _
+) As Object
+```
+
+**输入参数:**
+- `ws`: BOM库工作表对象(如"接头"、"弹性元件"等)
+- `params`: 从产品型号中提取的参数字典
+
+**输出结构:**
+```vba
+' 返回字典包含以下键:
+result("success") ' Boolean - 是否恰好匹配1条
+result("rowCount") ' Long - 匹配到的记录数量
+result("rowNums") ' Collection - 匹配到的行号集合
+result("message") ' String - 结果描述
+```
+
+### 2.2 匹配流程图
+
+```mermaid
+flowchart TD
+ Start([开始匹配]) --> InputValidate{验证输入}
+ InputValidate -->|ws为空| Error1[返回: 工作表为空]
+ InputValidate -->|params为空| Error2[返回: 参数字典为空]
+ InputValidate -->|验证通过| ReadData[读取工作表数据到数组]
+
+ ReadData --> CheckEmpty{是否有数据?}
+ CheckEmpty -->|无数据| Error3[返回: 工作表无数据]
+ CheckEmpty -->|有数据| BuildMap[构建表头映射]
+
+ BuildMap --> LoopStart[遍历数据行]
+ LoopStart --> EvalRow{评估该行
是否匹配?}
+ EvalRow -->|匹配| AddRow[添加到匹配集合]
+ EvalRow -->|不匹配| NextRow{下一行?}
+ AddRow --> NextRow
+
+ NextRow -->|是| LoopStart
+ NextRow -->|否| CheckCount{检查匹配数量}
+
+ CheckCount -->|0条| Error4[返回: 未找到匹配记录]
+ CheckCount -->|1条| Success[返回: 匹配成功]
+ CheckCount -->|2+条| Error5[返回: 匹配到N条记录]
+
+ style Success fill:#51cf66
+ style Error1 fill:#ff6b6b
+ style Error2 fill:#ff6b6b
+ style Error3 fill:#ff6b6b
+ style Error4 fill:#ff6b6b
+ style Error5 fill:#ff6b6b
+```
+
+### 2.3 EvaluateCellCondition() 单元格匹配规则
+
+`EvaluateCellCondition()` 是最核心的判断逻辑,决定单个单元格是否满足匹配条件。
+
+**函数签名:**
+```vba
+Public Function EvaluateCellCondition( _
+ ByVal cellValue As Variant, _
+ ByVal paramValue As String, _
+ ByVal fieldName As String _
+) As Boolean
+```
+
+#### 2.3.1 匹配规则优先级
+
+```mermaid
+flowchart TD
+ A[评估单元格条件] --> B{单元格为空?}
+ B -->|是| C[✅ 通配符匹配
返回True]
+
+ B -->|否| D{以!=开头?}
+ D -->|是| E[否定匹配
paramValue ≠ notValue?]
+
+ D -->|否| F{字段是fjgn?}
+ F -->|是| G[包含匹配
InStr判断]
+
+ F -->|否| H[精确匹配
paramValue = cellValue?]
+
+ E --> I{条件满足?}
+ G --> I
+ H --> I
+
+ I -->|是| J[返回True]
+ I -->|否| K[返回False]
+
+ style C fill:#51cf66
+ style J fill:#51cf66
+ style K fill:#ff6b6b
+```
+
+#### 2.3.2 详细规则说明
+
+| 规则类型 | BOM库单元格值 | 提取参数值 | 匹配结果 | 说明 |
+|---------|--------------|-----------|---------|------|
+| **空单元格** | `(空)` | 任意值 | ✅ 匹配 | 空单元格作为通配符,匹配所有值 |
+| **否定匹配** | `!=A0` | `A0` | ❌ 不匹配 | 参数值等于否定值时失败 |
+| **否定匹配** | `!=A0` | `AT` | ✅ 匹配 | 参数值不等于否定值时成功 |
+| **fjgn包含** | `N1` | `N1,N2,N3` | ✅ 匹配 | 使用`InStr()`判断包含关系 |
+| **fjgn包含** | `N4` | `N1,N2,N3` | ❌ 不匹配 | 不包含时失败 |
+| **精确匹配** | `A0` | `A0` | ✅ 匹配 | 字符串完全相等 |
+| **精确匹配** | `A0` | `AT` | ❌ 不匹配 | 字符串不相等 |
+
+**代码实现:**
+```vba
+' 1. 空单元格:通配符
+If IsEmpty(cellValue) Or Len(Trim(CStr(cellValue))) = 0 Then
+ EvaluateCellCondition = True
+ Exit Function
+End If
+
+' 2. 否定条件 (!=开头)
+If Left(cellStr, 2) = "!=" Then
+ notValue = Trim(Mid(cellStr, 3))
+ EvaluateCellCondition = (paramValue <> notValue)
+ Exit Function
+End If
+
+' 3. fjgn字段:包含匹配
+If LCase(fieldName) = "fjgn" Then
+ EvaluateCellCondition = CheckFjgnMatch(cellStr, paramValue)
+ Exit Function
+End If
+
+' 4. 精确匹配
+EvaluateCellCondition = (paramValue = cellStr)
+```
+
+### 2.4 AND逻辑要求
+
+匹配算法采用**AND逻辑**:所有参数列必须同时满足条件,该行才匹配成功。
+
+```mermaid
+graph LR
+ A[参数1: azxs=A0] --> B{匹配?}
+ B -->|否| C[❌ 整行不匹配]
+
+ B -->|是| D[参数2: gclj=M20]
+ D --> E{匹配?}
+ E -->|否| C
+
+ E -->|是| F[参数3: jycz=1]
+ F --> G{匹配?}
+ G -->|否| C
+ G -->|是| H[✅ 整行匹配]
+
+ style C fill:#ff6b6b
+ style H fill:#51cf66
+```
+
+**代码实现:**
+```vba
+Private Function EvaluateConditionRow( _
+ ByRef bomData As Variant, _
+ ByVal rowIdx As Long, _
+ ByVal headerMap As Object, _
+ ByVal params As Object _
+) As Boolean
+ Dim paramKey As Variant
+
+ ' 遍历所有参数
+ For Each paramKey In params.keys
+ Dim paramValue As String
+ paramValue = CStr(params(paramKey))
+
+ ' 检查BOM库中是否有该列
+ If headerMap.Exists(CStr(paramKey)) Then
+ Dim colIdx As Long
+ colIdx = headerMap(CStr(paramKey))
+
+ ' 获取单元格值
+ Dim cellValue As Variant
+ cellValue = bomData(rowIdx, colIdx)
+
+ ' 评估单元格条件
+ If Not EvaluateCellCondition(cellValue, paramValue, CStr(paramKey)) Then
+ ' 只要有一个条件不满足,该行就不匹配
+ EvaluateConditionRow = False
+ Exit Function
+ End If
+ End If
+ Next paramKey
+
+ ' 所有条件都满足
+ EvaluateConditionRow = True
+End Function
+```
+
+---
+
+## 3. 错误类型与检测
+
+### 3.1 错误类型总览
+
+```mermaid
+graph TD
+ A[BOM匹配错误] --> B[匹配数量错误]
+ A --> C[系统错误]
+ A --> D[部件验证错误]
+
+ B --> B1[0条匹配
No Match]
+ B --> B2[2+条匹配
Multiple Match]
+
+ C --> C1[工作表为空]
+ C --> C2[参数字典为空]
+ C --> C3[工作表无数据]
+ C --> C4[运行时异常]
+
+ D --> D1[部件+子件共存]
+ D --> D2[接头数量≠弹性元件数量]
+ D --> D3[缺少部件和子件]
+
+ style B1 fill:#ff6b6b
+ style B2 fill:#ff6b6b
+ style C1 fill:#ffa94d
+ style C2 fill:#ffa94d
+ style C3 fill:#ffa94d
+ style C4 fill:#ffa94d
+ style D1 fill:#845ef7
+ style D2 fill:#845ef7
+ style D3 fill:#845ef7
+```
+
+### 3.2 匹配数量错误
+
+#### 3.2.1 0条匹配 (No Match)
+
+**触发条件:** 在BOM库中未找到任何满足条件的记录
+
+**错误信息:** `"未找到匹配记录"`
+
+**常见原因:**
+1. BOM库中确实没有该配置的物料
+2. 产品型号解析错误(参数提取不正确)
+3. BOM库配置条件过于严格
+4. 参数值拼写错误
+
+**检测位置:** `M07_BOMMatcher.bas:128-130`
+```vba
+If matchingRows.count = 0 Then
+ result("success") = False
+ result("message") = "未找到匹配记录"
+End If
+```
+
+#### 3.2.2 2+条匹配 (Multiple Match)
+
+**触发条件:** 在BOM库中找到多条满足条件的记录
+
+**错误信息:** `"匹配到N条记录(需要恰好1条)"`
+
+**常见原因:**
+1. BOM库中存在重复记录
+2. 配置条件不够细化(列数不足或值过于宽泛)
+3. 空单元格过多(通配符导致模糊匹配)
+
+**检测位置:** `M07_BOMMatcher.bas:134-137`
+```vba
+Else
+ result("success") = False
+ result("message") = "匹配到" & matchingRows.count & "条记录(需要恰好1条)"
+End If
+```
+
+### 3.3 系统错误
+
+| 错误类型 | 错误信息 | 触发条件 | 检测位置 |
+|---------|---------|---------|---------|
+| 工作表为空 | `"工作表为空"` | `ws Is Nothing` | `M07_BOMMatcher.bas:62-68` |
+| 参数字典为空 | `"参数字典为空"` | `params Is Nothing Or params.count = 0` | `M07_BOMMatcher.bas:71-78` |
+| 工作表无数据 | `"工作表无数据"` | `lastRow < BOMLIB_START_ROW` | `M07_BOMMatcher.bas:90-96` |
+| 运行时异常 | `"系统错误: {Err.Description}"` | `Err.Number <> 0` | `M07_BOMMatcher.bas:142-152` |
+
+### 3.4 部件验证错误
+
+部件物料有特殊的组合验证规则,详见[第6章:部件特殊处理](#6-部件特殊处理)。
+
+---
+
+## 4. 决策树与流程图
+
+### 4.1 完整匹配流程决策树(两阶段验证)
+
+```mermaid
+flowchart TD
+ A[开始: 接收产品型号] --> B[解析型号提取参数]
+ B --> C{参数提取成功?}
+ C -->|否| Err1[记录错误: 型号解析失败]
+ C -->|是| D[阶段1: 收集所有工作表匹配结果]
+
+ D --> E{是'部件'工作表?}
+ E -->|是| F[调用M08_ComponentProcessor
存入resultsDict]
+ E -->|否| G[调用M07_BOMMatcher
存入resultsDict]
+
+ F --> H{更多工作表?}
+ G --> H
+ H -->|是| D
+ H -->|否| I[阶段2: 统一验证所有结果]
+
+ I --> J{基础验证
非特殊工作表}
+ J --> K{特殊验证
部件/接头/弹性元件}
+
+ K --> L{验证通过?}
+ L -->|否| ErrV[记录验证错误]
+ L -->|是| M{有警告?}
+
+ M -->|是| Warn[记录警告
不阻断流程]
+ M -->|否| N[生成输出行]
+ Warn --> N
+
+ Err1 --> O[写入错误报告]
+ ErrV --> O
+ Warn --> O
+ N --> P[写入结果工作表]
+
+ style Err1 fill:#ff6b6b
+ style ErrV fill:#ff6b6b
+ style Warn fill:#ffd43b
+ style N fill:#51cf66
+```
+
+### 4.2 错误检测决策树
+
+```mermaid
+flowchart TD
+ Start([匹配结果检测]) --> CheckCount{匹配数量?}
+
+ CheckCount -->|0| TypeA[No Match
未找到匹配记录]
+ CheckCount -->|1| Success[Success
匹配成功]
+ CheckCount -->|2+| TypeB[Multiple Match
匹配到N条记录]
+
+ TypeA --> AnalyzeA[分析原因]
+ TypeB --> AnalyzeB[分析原因]
+
+ AnalyzeA --> CauseA1{BOM库缺失?}
+ CauseA1 -->|是| FixA1[补充BOM库记录]
+ CauseA1 -->|否| CauseA2{解析错误?}
+ CauseA2 -->|是| FixA2[检查型号解析逻辑]
+ CauseA2 -->|否| FixA3[检查配置条件]
+
+ AnalyzeB --> CauseB1{重复记录?}
+ CauseB1 -->|是| FixB1[删除重复记录]
+ CauseB1 -->|否| CauseB2{条件过宽?}
+ CauseB2 -->|是| FixB2[细化配置列]
+ CauseB2 -->|否| FixB3[减少空单元格]
+
+ style Success fill:#51cf66
+ style TypeA fill:#ff6b6b
+ style TypeB fill:#ff6b6b
+ style FixA1 fill:#4dabf7
+ style FixA2 fill:#4dabf7
+ style FixA3 fill:#4dabf7
+ style FixB1 fill:#4dabf7
+ style FixB2 fill:#4dabf7
+ style FixB3 fill:#4dabf7
+```
+
+### 4.3 EvaluateCellCondition 逻辑流程
+
+```mermaid
+flowchart TD
+ A[开始评估单元格] --> B{cellValue为空
或IsEmpty?}
+
+ B -->|是| C[返回True
通配符匹配]
+
+ B -->|否| D[转换cellValue为字符串]
+ D --> E{以'!='开头?}
+
+ E -->|是| F[提取notValue]
+ F --> G{paramValue <> notValue?}
+ G -->|是| H[返回True]
+ G -->|否| I[返回False]
+
+ E -->|否| J{fieldName是'fjgn'?}
+
+ J -->|是| K[调用CheckFjgnMatch]
+ K --> L{InStr包含判断}
+ L -->|是| H
+ L -->|否| I
+
+ J -->|否| M[精确匹配]
+ M --> N{paramValue = cellStr?}
+ N -->|是| H
+ N -->|否| I
+
+ style C fill:#51cf66
+ style H fill:#51cf66
+ style I fill:#ff6b6b
+```
+
+### 4.4 部件验证流程
+
+```mermaid
+flowchart TD
+ A[验证部件组合] --> B{物料集合为空?}
+
+ B -->|是| Err1[返回: 物料列表为空]
+
+ B -->|否| C[统计各类型物料数量]
+
+ C --> D{有1个部件
且无子件?}
+ D -->|是| Success1[验证通过: 1个部件]
+
+ D -->|否| E{无部件
且有1个接头+1个弹性元件?}
+ E -->|是| Success2[验证通过: 1个接头+1个弹性元件]
+
+ E -->|否| F[组合异常判断]
+
+ F --> G{部件>1?}
+ G -->|是| Err2[部件数量为N(应为1)]
+
+ G -->|否| H{有部件且存在子件?}
+ H -->|是| Err3[同时存在部件和子件]
+
+ H -->|否| I{接头数≠弹性元件数?}
+ I -->|是| Err4[接头数N ≠ 弹性元件数M]
+
+ I -->|否| J{无部件且无子件?}
+ J -->|是| Err5[缺少部件和子件]
+
+ J -->|否| Err6[未知异常组合]
+
+ style Success1 fill:#51cf66
+ style Success2 fill:#51cf66
+ style Err1 fill:#ff6b6b
+ style Err2 fill:#ff6b6b
+ style Err3 fill:#ff6b6b
+ style Err4 fill:#ff6b6b
+ style Err5 fill:#ff6b6b
+ style Err6 fill:#ff6b6b
+```
+
+### 4.5 错误报告序列图
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant M09 as M09_BOMExtractor
+ participant M07 as M07_BOMMatcher
+ participant M08 as M08_ComponentProcessor
+ participant Logger as clsErrorLogger
+
+ User->>M09: 运行BOM提取
+ M09->>M07: MatchBOMRecord(ws, params)
+
+ alt 匹配失败
+ M07-->>M09: {success:False, message:"..."}
+ M09->>Logger: Record(0, "M09...", "BOMMatchError", message)
+ else 匹配成功
+ M07-->>M09: {success:True, rowNums:Collection}
+ end
+
+ M09->>M08: ValidateComponentCombination(materials)
+
+ alt 验证失败
+ M08-->>M09: {valid:False, message:"..."}
+ M09->>Logger: Record(0, "M09...", "ValidationError", message)
+ end
+
+ M09->>M09: 生成输出行(包含remarks)
+ M09->>Logger: HasErrors?
+
+ alt 有错误
+ Logger-->>M09: True
+ M09->>Logger: PrintReport(Workbook)
+ Logger->>Logger: 创建"错误报告_"工作表
+ end
+
+ M09-->>User: 返回处理结果消息
+```
+
+---
+
+## 5. 错误场景实例
+
+### 5.1 场景1:0条匹配 - BOM库缺失
+
+**输入条件:**
+- 产品型号:`YTHN-100 M20 A0 1 M01 N1`
+- 提取参数:`{azxs: "A0", gclj: "M20", jycz: "1", lcfw: "M01", fjgn: "N1"}`
+- BOM库"接头"工作表:
+ | azxs | gclj | jycz | lcfw | 物料名称 |
+ |------|------|------|------|----------|
+ | AT | M20 | 1 | M01 | 接头AT |
+ | AH | M20 | 1 | M01 | 接头AH |
+
+**匹配过程:**
+```vba
+' 第1行: azxs=AT vs param="A0"
+EvaluateCellCondition("AT", "A0", "azxs") → False (精确匹配失败)
+' 整行不匹配
+
+' 第2行: azxs=AH vs param="A0"
+EvaluateCellCondition("AH", "A0", "azxs") → False (精确匹配失败)
+' 整行不匹配
+
+' 最终结果: matchingRows.count = 0
+```
+
+**错误输出:**
+```javascript
+{
+ success: false,
+ rowCount: 0,
+ rowNums: [],
+ message: "未找到匹配记录"
+}
+```
+
+**解决方案:**
+1. 检查是否需要添加 `azxs=A0` 的记录到BOM库
+2. 确认型号解析是否正确(A0是否应为AT)
+
+### 5.2 场景2:0条匹配 - 否定条件不满足
+
+**输入条件:**
+- 提取参数:`{azxs: "A0", gclj: "M20"}`
+- BOM库"接头"工作表:
+ | azxs | gclj | 物料名称 |
+ |------|------|----------|
+ | !=A0 | M20 | 接头非A0 |
+
+**匹配过程:**
+```vba
+' azxs列: cellValue="!=A0", paramValue="A0"
+EvaluateCellCondition("!=A0", "A0", "azxs") → False (否定匹配: "A0" = "A0")
+' 整行不匹配
+
+' 最终结果: matchingRows.count = 0
+```
+
+**错误输出:**
+```javascript
+{
+ success: false,
+ rowCount: 0,
+ message: "未找到匹配记录"
+}
+```
+
+**解决方案:**
+- 该记录明确排除 `azxs=A0`,这是预期行为
+- 如需支持 `A0`,需添加新记录或修改否定条件
+
+### 5.3 场景3:2+条匹配 - 重复记录
+
+**输入条件:**
+- 提取参数:`{azxs: "A0", gclj: "M20"}`
+- BOM库"接头"工作表:
+ | azxs | gclj | lcfw | 物料名称 | 物料编码 |
+ |------|------|------|----------|----------|
+ | A0 | M20 | | 接头A0-1 | JT-001 |
+ | A0 | M20 | | 接头A0-2 | JT-002 |
+
+**匹配过程:**
+```vba
+' 第1行:
+' azxs: "A0" = "A0" → True
+' gclj: "M20" = "M20" → True
+' lcfw: "" (空) → True (通配符)
+' → 匹配成功
+
+' 第2行:
+' azxs: "A0" = "A0" → True
+' gclj: "M20" = "M20" → True
+' lcfw: "" (空) → True (通配符)
+' → 匹配成功
+
+' 最终结果: matchingRows.count = 2
+```
+
+**错误输出:**
+```javascript
+{
+ success: false,
+ rowCount: 2,
+ rowNums: [2, 3],
+ message: "匹配到2条记录(需要恰好1条)"
+}
+```
+
+**解决方案:**
+1. 删除重复记录
+2. 细化配置条件(如添加`lcfw`列区分)
+
+### 5.4 场景4:2+条匹配 - 空单元格过多
+
+**输入条件:**
+- 提取参数:`{azxs: "A0", gclj: "M20"}`
+- BOM库"接头"工作表:
+ | azxs | gclj | jycz | lcfw | fjgn | 物料名称 |
+ |------|------|------|------|------|----------|
+ | A0 | M20 | | | | 接头配置1 |
+ | A0 | M20 | | | | 接头配置2 |
+ | A0 | M20 | | | | 接头配置3 |
+
+**匹配过程:**
+```vba
+' 所有3行的 jycz、lcfw、fjgn 都是空单元格
+' 空单元格 → 通配符 → 全部返回True
+' 导致3行都匹配
+
+' 最终结果: matchingRows.count = 3
+```
+
+**错误输出:**
+```javascript
+{
+ success: false,
+ rowCount: 3,
+ rowNums: [2, 3, 4],
+ message: "匹配到3条记录(需要恰好1条)"
+}
+```
+
+**解决方案:**
+1. 补充缺失的配置列值
+2. 添加新的区分列(如`bkxs`、`特殊要求`等)
+
+### 5.5 场景5:fjgn包含匹配
+
+**输入条件:**
+- 提取参数:`{azxs: "A0", fjgn: "N1,N2,N3"}`
+- BOM库"接头"工作表:
+ | azxs | fjgn | 物料名称 |
+ |------|------|----------|
+ | A0 | N1 | 带N1功能 |
+ | A0 | N4 | 带N4功能 |
+
+**匹配过程:**
+```vba
+' 第1行:
+' azxs: "A0" = "A0" → True
+' fjgn: CheckFjgnMatch("N1", "N1,N2,N3")
+' → InStr("N1,N2,N3", "N1") > 0 → True
+' → 匹配成功
+
+' 第2行:
+' azxs: "A0" = "A0" → True
+' fjgn: CheckFjgnMatch("N4", "N1,N2,N3")
+' → InStr("N1,N2,N3", "N4") = 0 → False
+' → 不匹配
+
+' 最终结果: matchingRows.count = 1, success = true
+```
+
+**正确输出:**
+```javascript
+{
+ success: true,
+ rowCount: 1,
+ rowNums: [2],
+ message: "匹配成功"
+}
+```
+
+### 5.6 场景6:部件验证错误
+
+**输入条件:**
+- 提取参数:`{azxs: "A0", gclj: "M20"}`
+- 匹配结果物料集合:
+ ```javascript
+ [
+ {materialType: "接头", materialName: "接头A0", ...},
+ {materialType: "接头", materialName: "接头AT", ...}, // 重复
+ {materialType: "弹性元件", materialName: "元件M20", ...}
+ ]
+ ```
+
+**验证过程:**
+```vba
+' 统计:
+' componentCount = 0
+' jointCount = 2
+' elementCount = 1
+
+' 验证规则: jointCount (2) ≠ elementCount (1)
+' → 验证失败
+```
+
+**验证结果:**
+```javascript
+{
+ valid: false,
+ message: "部件组合异常: 接头数量(2)≠弹性元件数量(1)"
+}
+```
+
+**错误输出行:**
+| 原始产品型号 | azxs | ... | 物料类型 | 物料名称 | 提取备注 |
+|-------------|------|-----|----------|----------|----------|
+| YTHN-100... | A0 | ... | 接头 | 接头A0 | 部件组合异常: 接头数量(2)≠弹性元件数量(1) |
+
+**解决方案:**
+1. 检查BOM库配置是否导致多次匹配
+2. 确保接头和弹性元件一对一配对
+
+---
+
+## 6. 部件特殊处理
+
+### 6.1 部件物料特性
+
+部件物料在AutoBOM系统中具有特殊地位:
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ 部件物料结构 │
+├──────────────────────────────────────────────────────────┤
+│ 1条"部件"记录 = { │
+│ - 部件本体(优先选择) │
+│ - 子件1: 接头(备选方案) │
+│ - 子件2: 弹性元件(备选方案) │
+│ } │
+│ │
+│ 选择逻辑: │
+│ ✓ 有库存 → 返回部件 │
+│ ✗ 无库存 → 返回接头 + 弹性元件 │
+└──────────────────────────────────────────────────────────┘
+```
+
+### 6.2 部件处理流程
+
+```mermaid
+flowchart TD
+ A[开始处理'部件'工作表] --> B[调用MatchBOMRecord]
+
+ B --> C{匹配成功?}
+ C -->|否| Err[记录匹配错误
添加错误物料]
+
+ C -->|是| D[获取匹配行号]
+ D --> E[构建表头映射]
+
+ E --> F{检查部件库存}
+ F -->|有库存| G[提取部件物料信息]
+ F -->|无库存| H[提取子件信息]
+
+ G --> I[返回部件物料集合]
+
+ H --> J[提取接头信息]
+ H --> K[提取弹性元件信息]
+ J --> L[返回子件集合]
+ K --> L
+
+ style Err fill:#ff6b6b
+ style G fill:#51cf66
+ style L fill:#51cf66
+```
+
+### 6.3 部件组合验证规则(两阶段验证)
+
+#### ✅ 正确组合(验证通过)
+
+| 组合类型 | 部件数 | 接头数 | 弹性元件数 | 状态 | 说明 |
+|---------|-------|-------|-----------|------|------|
+| 纯部件 | 1 | 0 | 0 | ✅ 有效 | [部件]工作表返回1个部件 |
+| 纯子件 | 0 | 1 | 1 | ✅ 有效 | [部件]工作表返回1个接头+1个弹性元件 |
+| 独立子件 | 0 | 1 | 1 | ✅ 有效 | [接头]和[弹性元件]工作表各返回1条 |
+
+#### ⚠️ 警告组合(验证通过,记录警告)
+
+| 组合类型 | 部件数 | 接头数 | 弹性元件数 | 警告类型 | 说明 |
+|---------|-------|-------|-----------|---------|------|
+| 部件+接头 | 1 | 1+ | 任意 | ComponentConflict | 存在[部件]物料,但[接头]工作表也匹配到,已忽略 |
+| 部件+弹性元件 | 1 | 任意 | 1+ | ComponentConflict | 存在[部件]物料,但[弹性元件]工作表也匹配到,已忽略 |
+| 子件+独立接头 | 0 | 2+ | 1+ | ComponentConflict | [部件]工作表已返回接头,但[接头]工作表也匹配到,已忽略 |
+| 子件+独立元件 | 0 | 1+ | 2+ | ComponentConflict | [部件]工作表已返回弹性元件,但[弹性元件]工作表也匹配到,已忽略 |
+
+#### ❌ 异常组合(验证失败)
+
+| 异常类型 | 部件数 | 接头数 | 弹性元件数 | 错误信息 |
+|---------|-------|-------|-----------|---------|
+| 部件过多(部件表) | >1 | - | - | [部件]工作表匹配到N条记录 |
+| 部件过多(总计数) | >1 | - | - | 部件数量为N(应为1) |
+| 接头过多(部件表) | 0 | 2+ | 任意 | [部件]工作表匹配到2+个接头 |
+| 接头过多(接头表) | 0 | 2+ | 任意 | [接头]工作表匹配到N条记录 |
+| 弹性元件过多(部件表) | 0 | 任意 | 2+ | [部件]工作表匹配到2+个弹性元件 |
+| 弹性元件过多(元件表) | 0 | 任意 | 2+ | [弹性元件]工作表匹配到N条记录 |
+| 接头弹性不匹配 | 0 | N | M (N≠M) | 接头数量N ≠ 弹性元件数量M |
+| 完全缺失 | 0 | 0 | 0 | 部件、接头、弹性元件均未匹配或组合不完整 |
+| 数量不对称 | 0 | 1 | 0 | 接头和弹性元件数量不匹配 |
+| 数量不对称 | 0 | 0 | 1 | 接头和弹性元件数量不匹配 |
+
+### 6.4 ValidateComponentCombination() 代码逻辑
+
+```vba
+Public Function ValidateComponentCombination(ByVal materials As Collection) As Object
+ Dim componentCount As Long, jointCount As Long, elementCount As Long
+
+ ' 统计各类型物料数量
+ For Each mat In materials
+ Select Case mat("materialType")
+ Case "部件": componentCount = componentCount + 1
+ Case "接头": jointCount = jointCount + 1
+ Case "弹性元件": elementCount = elementCount + 1
+ End Select
+ Next mat
+
+ ' 规则1: 只有1个部件
+ If componentCount = 1 And jointCount = 0 And elementCount = 0 Then
+ result("valid") = True
+ result("message") = "验证通过:1个部件"
+ Exit Function
+ End If
+
+ ' 规则2: 没有部件,恰好1个接头和1个弹性元件
+ If componentCount = 0 And jointCount = 1 And elementCount = 1 Then
+ result("valid") = True
+ result("message") = "验证通过:1个接头+1个弹性元件"
+ Exit Function
+ End If
+
+ ' 其他情况都是异常
+ result("valid") = False
+ result("message") = BuildErrorMessage(componentCount, jointCount, elementCount)
+End Function
+```
+
+### 6.5 库存检查接口(预留)
+
+当前版本的库存检查函数默认返回 `True`(库存充足),预留接口供未来扩展:
+
+```vba
+Private Function CheckComponentInventory( _
+ ByVal wsComponent As Worksheet, _
+ ByVal rowNum As Long, _
+ ByVal headerMap As Object _
+) As Boolean
+ ' TODO: 连接库存系统查询实际库存
+ ' 示例扩展:
+ ' If headerMap.Exists("库存数量") Then
+ ' stockQty = CLng(wsComponent.Cells(rowNum, headerMap("库存数量")).Value)
+ ' CheckComponentInventory = (stockQty > 0)
+ ' End If
+
+ CheckComponentInventory = True ' 当前默认有库存
+End Function
+```
+
+**未来扩展方向:**
+1. 查询ERP系统API
+2. 读取库存Excel表
+3. 连接数据库库存表
+
+---
+
+## 7. 错误报告机制
+
+### 7.1 clsErrorLogger 类模块(v2.0)
+
+`clsErrorLogger` 是系统的错误日志记录器,负责收集、存储和报告所有匹配错误和警告。
+
+**类结构(v2.0):**
+```vba
+' 私有变量
+Private pErrors As Collection ' 错误集合
+Private pWarnings As Collection ' 警告集合
+
+' 公开方法
+Public Sub Record(RowIndex As Long, SourceFunc As String, ErrorType As String, Desc As String, Context As String)
+Public Sub RecordWarning(RowIndex As Long, SourceFunc As String, WarningType As String, Desc As String, Context As String)
+Public Property Get HasErrors() As Boolean
+Public Property Get HasWarnings() As Boolean
+Public Property Get HasIssues() As Boolean ' 有错误或警告
+Public Sub PrintReport(targetWb As Workbook)
+```
+
+**v2.0 新增功能:**
+- ✅ 支持警告类型(`RecordWarning`方法)
+- ✅ 区分错误和警告(`HasErrors`、`HasWarnings`、`HasIssues`属性)
+- ✅ 错误报告增加"类型"列(错误/警告)
+- ✅ 警告行黄色高亮,错误行红色高亮
+
+### 7.2 错误和警告记录结构
+
+#### 7.2.1 错误记录(Record方法)
+
+每条错误记录包含5个字段:
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| RowIndex | Long | 发生错误的行号 | 0(无特定行) |
+| SourceFunc | String | 来源模块/函数 | "M09.ValidateAllMatchResults" |
+| ErrorType | String | 错误类型 | "BOMMatchError" |
+| Desc | String | 详细描述 | "接头 未找到匹配记录" |
+| Context | String | 原始数据上下文 | ""(可选) |
+
+**错误类型枚举:**
+- `BOMMatchError`: BOM库匹配错误(0条或多条)
+- `ValidationError`: 部件组合验证错误
+- `SystemError`: 系统运行时异常
+
+#### 7.2.2 警告记录(RecordWarning方法)
+
+每条警告记录包含5个字段:
+
+| 字段名 | 类型 | 说明 | 示例 |
+|--------|------|------|------|
+| RowIndex | Long | 发生警告的行号 | 0(无特定行) |
+| SourceFunc | String | 来源模块/函数 | "M09.ValidateAllMatchResults" |
+| WarningType | String | 警告类型 | "ComponentConflict" |
+| Desc | String | 详细描述 | "存在[部件]物料,但[接头]工作表也匹配到1条记录,已忽略" |
+| Context | String | 原始数据上下文 | ""(可选) |
+
+**警告类型枚举:**
+- `ComponentConflict`: 部件与子件冲突(非阻断性)
+
+### 7.3 错误记录流程
+
+```mermaid
+sequenceDiagram
+ participant Func as 业务函数
+ participant Logger as clsErrorLogger
+ participant Collection as pErrors Collection
+ participant WB as Workbook
+
+ Func->>Logger: Record(0, "M09...", "BOMMatchError", "接头 未匹配", "")
+ activate Logger
+ Logger->>Collection: Add Array(0, "M09...", "BOMMatchError", "接头 未匹配", "")
+ Note over Collection: 存储到内存集合
+ Collection--xLogger: 确认添加
+ deactivate Logger
+
+ Note over Func: 继续处理其他物料...
+
+ Func->>Logger: HasErrors?
+ Logger->>Logger: pErrors.count > 0
+ Logger-->>Func: True
+
+ Func->>Logger: PrintReport(Workbook)
+ activate Logger
+ Logger->>WB: Worksheets.Add()
+ Logger->>WB: 写入表头 (A1:E1)
+ Logger->>Collection: 遍历所有错误
+ loop 每条错误记录
+ Collection-->>Logger: Array(RowIndex, SourceFunc, ErrorType, Desc, Context)
+ Logger->>WB: 写入到数据行
+ end
+ Logger->>WB: Columns.AutoFit()
+ Logger--xFunc: 完成
+ deactivate Logger
+```
+
+### 7.4 错误报告格式(v2.0)
+
+生成的错误报告工作表包含**6列**:
+
+| 类型 | 原表行号 | 来源模块 | 错误类型 | 详细描述 | 原始数据 |
+|------|---------|---------|---------|---------|---------|
+| 错误 | 0 | M09.ValidateAllMatchResults | BOMMatchError | 接头 未找到匹配记录 | |
+| 错误 | 0 | M09.ValidateAllMatchResults | BOMMatchError | 弹性元件 未找到匹配记录 | |
+| 警告 | 0 | M09.ValidateAllMatchResults | ComponentConflict | 存在[部件]物料,但[接头]工作表也匹配到1条记录,已忽略 | |
+| 错误 | 0 | M09.ProcessSingleModel | ValidationError | 部件组合异常: 接头数量(2)≠弹性元件数量(1) | |
+
+**格式化特性(v2.0):**
+- ✅ 表头加粗、灰色背景
+- ✅ 错误行红色背景
+- ✅ 警告行黄色背景
+- ✅ 自动列宽调整
+- ✅ 工作表命名: `"错误报告_" & Format(Now, "hhmmss")`
+
+### 7.5 错误和警告信息传播路径(v2.0)
+
+```mermaid
+graph LR
+ A[M07_BOMMatcher.MatchBOMRecord] -->|返回结果| B[M09_BOMExtractor.MatchAllMaterialTypesWithValidation]
+ B -->|存入resultsDict| C[ValidateAllMatchResults]
+
+ C -->|检测到错误| D[clsErrorLogger.Record]
+ C -->|检测到警告| E[clsErrorLogger.RecordWarning]
+
+ D --> F[存储到pErrors集合]
+ E --> G[存储到pWarnings集合]
+
+ F --> H{HasIssues?}
+ G --> H
+ H -->|True| I[Logger.PrintReport]
+ H -->|False| J[跳过报告生成]
+
+ I --> K[生成错误和警告报告工作表]
+```
+
+### 7.6 用户通知机制(v2.0)
+
+在 `M09_BOMExtractor.RunBOMExtraction()` 函数中,通过返回消息通知用户:
+
+```vba
+Dim msg As String
+msg = "BOM提取完成!" & vbCrLf & _
+ "处理型号数: " & totalModels & vbCrLf & _
+ "提取物料数: " & allResults.count & vbCrLf & _
+ "成功数: " & successCount & vbCrLf & _
+ "异常数: " & errorCount
+
+If g_Logger.HasErrors Then
+ msg = msg & vbCrLf & vbCrLf & "发现错误,已生成错误报告工作表。"
+ElseIf g_Logger.HasWarnings Then
+ msg = msg & vbCrLf & vbCrLf & "发现警告,已生成错误报告工作表。"
+End If
+
+RunBOMExtraction = msg
+```
+
+**示例消息(有错误):**
+```
+BOM提取完成!
+处理型号数: 100
+提取物料数: 350
+成功数: 320
+异常数: 30
+
+发现错误,已生成错误报告工作表。
+```
+
+**示例消息(仅警告):**
+```
+BOM提取完成!
+处理型号数: 100
+提取物料数: 350
+成功数: 350
+异常数: 0
+
+发现警告,已生成错误报告工作表。
+```
+
+**示例消息(无问题):**
+```
+BOM提取完成!
+处理型号数: 100
+提取物料数: 350
+成功数: 350
+异常数: 0
+```
+
+---
+
+## 8. 快速参考
+
+### 8.1 匹配规则速查表
+
+| 规则类型 | BOM库单元格 | 提取参数 | 匹配结果 | 代码位置 |
+|---------|------------|---------|---------|---------|
+| **通配符** | `(空)` | 任意值 | ✅ 匹配 | `M07_BOMMatcher.bas:237-241` |
+| **否定** | `!=A0` | `A0` | ❌ 不匹配 | `M07_BOMMatcher.bas:247-252` |
+| **否定** | `!=A0` | `AT` | ✅ 匹配 | `M07_BOMMatcher.bas:247-252` |
+| **fjgn包含** | `N1` | `N1,N2` | ✅ 匹配 | `M07_BOMMatcher.bas:282-301` |
+| **精确** | `A0` | `A0` | ✅ 匹配 | `M07_BOMMatcher.bas:261-262` |
+| **精确** | `A0` | `AT` | ❌ 不匹配 | `M07_BOMMatcher.bas:261-262` |
+
+### 8.2 错误和警告类型速查表(v2.0)
+
+#### 错误类型
+
+| 错误类型 | 错误信息 | 触发条件 | 处理建议 |
+|---------|---------|---------|---------|
+| **No Match** | `未找到匹配记录` | 匹配0条 | 补充BOM库或检查参数 |
+| **Multiple Match** | `匹配到N条记录(需要恰好1条)` | 匹配2+条 | 删除重复或细化条件 |
+| **工作表为空** | `工作表为空` | `ws Is Nothing` | 检查BOM库文件 |
+| **参数字典为空** | `参数字典为空` | `params.count = 0` | 检查型号解析 |
+| **工作表无数据** | `工作表无数据` | `lastRow < START_ROW` | 检查BOM库数据 |
+| **部件组合异常** | `同时存在部件和子件` | 验证失败 | 检查部件处理逻辑 |
+| **部件、接头、弹性元件均未匹配** | `部件、接头、弹性元件均未匹配或组合不完整` | 特殊验证失败 | 检查BOM库配置 |
+| **接头和弹性元件数量不匹配** | `接头和弹性元件数量不匹配` | 特殊验证失败 | 确保接头和弹性元件一对一 |
+
+#### 警告类型(v2.0新增)
+
+| 警告类型 | 警告信息 | 触发条件 | 影响 |
+|---------|---------|---------|------|
+| **ComponentConflict** | `存在[部件]物料,但[接头]工作表也匹配到N条记录,已忽略` | 部件表返回部件,接头表也有匹配 | ⚠️ 非阻断,仅记录 |
+| **ComponentConflict** | `[部件]工作表已返回接头,但[接头]工作表也匹配到N条记录,已忽略` | 部件表返回子件,接头表也有匹配 | ⚠️ 非阻断,仅记录 |
+
+### 8.3 部件验证规则速查(v2.0)
+
+#### 验证结果分类
+
+| 部件数 | 接头数 | 弹性元件数 | 验证结果 | 说明 |
+|-------|-------|-----------|---------|------|
+| 1 | 0 | 0 | ✅ 有效 | 纯部件组合 |
+| 0 | 1 | 1 | ✅ 有效 | 纯子件组合 |
+| 1 | 1+ | 任意 | ⚠️ 警告 | 部件+接头冲突 |
+| 1 | 任意 | 1+ | ⚠️ 警告 | 部件+弹性元件冲突 |
+| >1 | - | - | ❌ 异常 | 部件过多 |
+| 1 | >0 | >0 | ❌ 异常 | 部件子件共存(旧逻辑) |
+| 0 | N | M (N≠M) | ❌ 异常 | 数量不匹配 |
+| 0 | 0 | 0 | ❌ 异常 | 完全缺失 |
+
+#### 警告场景详解(v2.0新增)
+
+| 场景 | [部件]工作表 | [接头]工作表 | [弹性元件]工作表 | 结果 |
+|------|------------|------------|---------------|------|
+| 部件+接头冲突 | 返回1个部件 | 返回1+个接头 | 任意或无 | ⚠️ 使用部件,忽略接头表 |
+| 部件+元件冲突 | 返回1个部件 | 任意或无 | 返回1+个弹性元件 | ⚠️ 使用部件,忽略弹性元件表 |
+| 子件+独立接头 | 返回1个接头+1个弹性元件 | 返回1+个接头 | 返回1+个弹性元件 | ⚠️ 使用部件表的子件,忽略独立表 |
+| 子件+独立元件 | 返回1个接头+1个弹性元件 | 返回1+个接头 | 返回1+个弹性元件 | ⚠️ 使用部件表的子件,忽略独立表 |
+
+### 8.4 故障排查指南
+
+#### 问题:未找到匹配记录
+
+**排查步骤:**
+1. ✅ 检查型号解析是否正确
+2. ✅ 确认BOM库中是否有对应记录
+3. ✅ 检查配置条件是否过于严格
+4. ✅ 验证否定条件(`!=`)是否误排除
+
+#### 问题:匹配到多条记录
+
+**排查步骤:**
+1. ✅ 检查是否有重复记录
+2. ✅ 统计空单元格数量(过多会导致模糊匹配)
+3. ✅ 评估是否需要添加区分列
+4. ✅ 确认配置列值是否足够细化
+
+#### 问题:部件组合验证失败
+
+**排查步骤:**
+1. ✅ 确认BOM库"接头"和"弹性元件"是否都匹配
+2. ✅ 检查是否存在重复匹配(如接头匹配到2条)
+3. ✅ 验证部件库存检查逻辑(如已实现)
+4. ✅ 审查M08_ComponentProcessor日志输出
+
+### 8.5 关键函数位置索引(v2.0)
+
+| 函数名 | 模块 | 行号 | 功能描述 |
+|--------|------|------|---------|
+| `MatchBOMRecord` | M07_BOMMatcher | 55-153 | BOM库匹配主入口 |
+| `EvaluateCellCondition` | M07_BOMMatcher | 230-266 | 单元格条件评估 |
+| `CheckFjgnMatch` | M07_BOMMatcher | 282-301 | fjgn包含匹配 |
+| `ProcessComponentRecord` | M08_ComponentProcessor | 58-137 | 部件处理主入口 |
+| `ValidateComponentCombination` | M08_ComponentProcessor | 423-507 | 部件组合验证(已弃用,移至M09) |
+| `ProcessSingleModel` | M09_BOMExtractor | 304-365 | 单型号处理流程 |
+| `MatchAllMaterialTypesWithValidation` | M09_BOMExtractor | 387-550 | **新增** 两阶段匹配(收集→验证) |
+| `ValidateAllMatchResults` | M09_BOMExtractor | 552-750 | **新增** 统一验证所有匹配结果 |
+| `ToArray` | M09_BOMExtractor | 751-762 | **新增** Collection转数组辅助函数 |
+| `Record` | clsErrorLogger | 16-19 | 错误记录 |
+| `RecordWarning` | clsErrorLogger | 37-39 | **新增** 警告记录 |
+| `HasWarnings` | clsErrorLogger | 27-29 | **新增** 是否有警告 |
+| `HasIssues` | clsErrorLogger | 32-34 | **新增** 是否有问题(错误或警告) |
+| `PrintReport` | clsErrorLogger | 42-105 | 生成错误报告(支持警告) |
+
+### 8.6 调试技巧
+
+#### 1. 启用调试输出
+
+在VBA编辑器中设置**立即窗口**可见(Ctrl+G),查看调试输出:
+
+```vba
+Debug.Print "=== 处理工作表: [" & sheetName & "] ==="
+Debug.Print " -> 匹配结果: " & matchResult("success") & ", 行数: " & matchResult("rowCount")
+Debug.Print " -> 匹配行号: " & rowNum
+```
+
+#### 2. 断点设置
+
+在关键位置设置断点,逐步执行:
+- `M07_BOMMatcher.MatchBOMRecord` - 观察匹配过程
+- `M09_BOMExtractor.MatchAllMaterialTypes` - 观察错误记录
+- `clsErrorLogger.Record` - 观察错误捕获
+
+#### 3. 监视变量
+
+添加监视变量:
+- `params` - 查看提取的参数字典
+- `matchingRows` - 查看匹配行集合
+- `result("message")` - 查看错误消息
+
+#### 4. 单元测试
+
+参考 `M99_TestRunner.bas` 编写单元测试:
+
+```vba
+Sub Test_BOMMatcher_NoMatch()
+ Dim params As Object
+ Set params = CreateObject("Scripting.Dictionary")
+ params.Add "azxs", "INVALID"
+
+ Dim result As Object
+ Set result = M07_BOMMatcher.MatchBOMRecord(wsJoint, params)
+
+ Debug.Assert result("success") = False
+ Debug.Assert result("rowCount") = 0
+ Debug.Assert InStr(result("message"), "未找到") > 0
+End Sub
+```
+
+---
+
+## 附录A:相关文档
+
+- [M06_ModelParser 型号解析机制](./M06_ModelParser_型号解析机制.md) - 了解参数提取过程
+- [M08_ComponentProcessor 部件处理逻辑](./M08_ComponentProcessor_部件处理逻辑.md) - 深入理解部件特殊处理
+- [RunBOMExtraction 完整流程](./RunBOMExtraction_流程详解.md) - 端到端流程说明
+
+---
+
+## 附录B:修订历史
+
+| 版本 | 日期 | 作者 | 修订内容 |
+|------|------|------|---------|
+| 1.0 | 2025-02-12 | Claude | 初始版本,完整覆盖错误判断机制 |
+| 2.0 | 2025-02-12 | Claude | **重大更新**:实施两阶段验证机制,新增警告类型,更新部件/接头/弹性元件交叉验证 |
+
+---
+
+**文档状态:** ✅ 完成
+**最后更新:** 2025-02-12
+**维护者:** AutoBOM开发团队