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开发团队