feat: implement BOM auto-extraction system
All checks were successful
NTFY Notification / notify (push) Successful in 11s
All checks were successful
NTFY Notification / notify (push) Successful in 11s
Add complete BOM auto-extraction system with the following modules: - M06_ModelParser: Parse product model strings to extract parameters (azxs, bkxs, gclj, jycz, lcfw, fjgn) - Extracts header part from full model (ignores dial/attachment parts) - Splits process connection and material code (G123 -> G12, 3) - Supports multiple additional features with comma/dot separators - M07_BOMMatcher: Match materials in BOM library - Exact match, wildcard (empty cell), negative match (!=) - Special fjgn contains matching logic - Array-based performance optimization for bulk operations - M08_ComponentProcessor: Handle component material special logic - Component inventory check (interface reserved) - Sub-component extraction (joint + elastic element) - Combination validation rules (1 component OR 1 joint + 1 element) - M09_BOMExtractor: Main extraction orchestrator - Reads input models from worksheet - Processes each model and matches all material types - Outputs to "BOM提取结果" worksheet - Error reporting and non-blocking design - M06B_TestRunner: Comprehensive unit tests - 8 test cases for model parsing - 5 test cases for BOM matching - 5 test cases for component processing - M04_Config: Add BOM extraction constants - BOM library filename and configuration - Input/output column definitions - Output column enumeration - M01_Main: Add RunBOMExtraction entry point Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
299
VBA_BOMConverter/Modules/M06_ModelParser.bas
Normal file
299
VBA_BOMConverter/Modules/M06_ModelParser.bas
Normal file
@@ -0,0 +1,299 @@
|
||||
' ==============================================================================
|
||||
' 模块: M06_ModelParser
|
||||
' 职责: 产品型号解析,从完整产品型号中提取关键参数
|
||||
'
|
||||
' 产品型号结构: [表头]|[表盘]|[附件]|[法兰隔膜]
|
||||
' 表头结构: [型号]-[公称外径].[安装形式].[壳体形式].[过程连接&接液材质].[量程范围].[仪表特性]
|
||||
'
|
||||
' 示例: YTHN-100.A0.531.G123.M04.Y3|BP-088.2312.B09.0A3
|
||||
' 表头: YTHN-100.A0.531.G123.M04.Y3
|
||||
' azxs: A0, bkxs: 531, gclj: G12, jycz: 3, lcfw: M04, fjgn: Y3
|
||||
'
|
||||
' 注意: 仅处理表头部分,其他部分(表盘、附件、法兰隔膜)暂时丢弃
|
||||
' ==============================================================================
|
||||
Option Explicit
|
||||
|
||||
' 模块级变量 - 错误记录器
|
||||
Private g_Logger As clsErrorLogger
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 初始化型号解析器
|
||||
' ------------------------------------------------------------------------------
|
||||
Public Sub InitModelParser(logger As clsErrorLogger)
|
||||
Set g_Logger = logger
|
||||
End Sub
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 主入口: 解析产品型号
|
||||
'
|
||||
' 输入:
|
||||
' modelString - 完整的产品型号字符串
|
||||
'
|
||||
' 输出:
|
||||
' Object (Scripting.Dictionary) - 包含提取的参数
|
||||
' 键值对: "xh"->型号, "gcwj"->公称外径, "azxs"->安装形式,
|
||||
' "bkxs"->表壳形式, "gclj"->过程连接, "jycz"->接液材质,
|
||||
' "lcfw"->量程范围, "fjgn"->附加功能
|
||||
'
|
||||
' 示例:
|
||||
' Set params = ParseProductModel("YTHN-100.A0.531.G123.M04.Y3|BP-088.2312.B09.0A3")
|
||||
' ' params("azxs") = "A0"
|
||||
' ' params("gclj") = "G12"
|
||||
' ' params("jycz") = "3"
|
||||
' ------------------------------------------------------------------------------
|
||||
Public Function ParseProductModel(ByVal modelString As String) As Object
|
||||
On Error GoTo ErrorHandler
|
||||
|
||||
Dim params As Object
|
||||
Set params = CreateObject("Scripting.Dictionary")
|
||||
|
||||
' 预处理:去除前后空格
|
||||
modelString = Trim(modelString)
|
||||
|
||||
' 如果型号为空,返回空字典
|
||||
If Len(modelString) = 0 Then
|
||||
Set ParseProductModel = params
|
||||
Exit Function
|
||||
End If
|
||||
|
||||
' 步骤1: 提取表头部分(第一个管道符之前的部分)
|
||||
Dim headerPart As String
|
||||
headerPart = ExtractHeaderPart(modelString)
|
||||
|
||||
If Len(headerPart) = 0 Then
|
||||
If Not g_Logger Is Nothing Then
|
||||
g_Logger.Record 0, "M06.ParseProductModel", "ModelParseError", _
|
||||
"无法提取表头部分,型号可能为空或格式错误", modelString
|
||||
End If
|
||||
Set ParseProductModel = params
|
||||
Exit Function
|
||||
End If
|
||||
|
||||
' 步骤2: 分割表头为段
|
||||
Dim segments As Variant
|
||||
segments = SplitHeaderPart(headerPart)
|
||||
|
||||
If Not IsArray(segments) Then
|
||||
If Not g_Logger Is Nothing Then
|
||||
g_Logger.Record 0, "M06.ParseProductModel", "ModelParseError", _
|
||||
"表头分割失败", headerPart
|
||||
End If
|
||||
Set ParseProductModel = params
|
||||
Exit Function
|
||||
End If
|
||||
|
||||
' 步骤3: 验证段数是否足够
|
||||
If UBound(segments) - LBound(segments) + 1 < MODEL_HEADER_MIN_SEGMENTS Then
|
||||
If Not g_Logger Is Nothing Then
|
||||
g_Logger.Record 0, "M06.ParseProductModel", "ModelParseError", _
|
||||
"表头段数不足,需要至少" & MODEL_HEADER_MIN_SEGMENTS & "段,实际" & _
|
||||
(UBound(segments) - LBound(segments) + 1) & "段", headerPart
|
||||
End If
|
||||
End If
|
||||
|
||||
' 步骤4: 提取型号和公称外径(预留,暂不参与BOM匹配)
|
||||
Call ExtractModelAndSize(headerPart, params)
|
||||
|
||||
' 步骤5: 提取各参数
|
||||
' 注意:segments(0)是"型号-公称外径",需要从segments(1)开始提取参数
|
||||
If UBound(segments) - LBound(segments) + 1 >= 2 Then
|
||||
params("azxs") = ExtractAzxs(segments(1))
|
||||
End If
|
||||
|
||||
If UBound(segments) - LBound(segments) + 1 >= 3 Then
|
||||
params("bkxs") = ExtractBkxs(segments(2))
|
||||
End If
|
||||
|
||||
If UBound(segments) - LBound(segments) + 1 >= 4 Then
|
||||
Dim gclj As String, jycz As String
|
||||
Call ExtractGcljAndJycz(segments(3), gclj, jycz)
|
||||
params("gclj") = gclj
|
||||
params("jycz") = jycz
|
||||
End If
|
||||
|
||||
If UBound(segments) - LBound(segments) + 1 >= 5 Then
|
||||
params("lcfw") = ExtractLcfw(segments(4))
|
||||
End If
|
||||
|
||||
If UBound(segments) - LBound(segments) + 1 >= 6 Then
|
||||
params("fjgn") = ExtractFjgn(segments, 5)
|
||||
End If
|
||||
|
||||
Set ParseProductModel = params
|
||||
Exit Function
|
||||
|
||||
ErrorHandler:
|
||||
If Not g_Logger Is Nothing Then
|
||||
g_Logger.Record 0, "M06.ParseProductModel", "SystemError", _
|
||||
"解析过程发生错误: " & Err.Description, modelString
|
||||
End If
|
||||
Set ParseProductModel = CreateObject("Scripting.Dictionary")
|
||||
End Function
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取表头部分(管道符之前)
|
||||
'
|
||||
' 输入: YTHN-100.A0.531.G123.M04.Y3|BP-088.2312.B09.0A3
|
||||
' 输出: YTHN-100.A0.531.G123.M04.Y3
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Function ExtractHeaderPart(ByVal fullModel As String) As String
|
||||
Dim pipePos As Long
|
||||
pipePos = InStr(fullModel, MODEL_SEPARATOR_PIPELINE)
|
||||
|
||||
If pipePos > 0 Then
|
||||
ExtractHeaderPart = Left(fullModel, pipePos - 1)
|
||||
Else
|
||||
ExtractHeaderPart = fullModel
|
||||
End If
|
||||
End Function
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 分割表头部分为段数组
|
||||
'
|
||||
' 输入: YTHN-100.A0.531.G123.M04.Y3
|
||||
' 输出: Array("YTHN-100", "A0", "531", "G123", "M04", "Y3")
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Function SplitHeaderPart(ByVal headerPart As String) As Variant
|
||||
' 按点号分割
|
||||
Dim rawSegments As Variant
|
||||
rawSegments = Split(headerPart, MODEL_SEPARATOR_DOT)
|
||||
|
||||
' 如果分割失败或结果为空
|
||||
If Not IsArray(rawSegments) Then
|
||||
SplitHeaderPart = Null
|
||||
Exit Function
|
||||
End If
|
||||
|
||||
' 去除每个段的空格
|
||||
Dim i As Long
|
||||
For i = LBound(rawSegments) To UBound(rawSegments)
|
||||
rawSegments(i) = Trim(CStr(rawSegments(i)))
|
||||
Next i
|
||||
|
||||
SplitHeaderPart = rawSegments
|
||||
End Function
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取型号和公称外径(预留字段)
|
||||
'
|
||||
' 输入: YTHN-100
|
||||
' 输出: params("xh")="YTHN", params("gcwj")="100"
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Sub ExtractModelAndSize(ByVal firstSegment As String, ByRef params As Object)
|
||||
Dim dashPos As Long
|
||||
dashPos = InStr(firstSegment, "-")
|
||||
|
||||
If dashPos > 1 Then
|
||||
params("xh") = Left(firstSegment, dashPos - 1)
|
||||
Dim afterDash As String
|
||||
afterDash = Mid(firstSegment, dashPos + 1)
|
||||
|
||||
' 提取-号后的数字部分(公称外径)
|
||||
' 因为firstSegment是"YTHN-100.A0...",需要去掉后面的点号内容
|
||||
Dim dotPos As Long
|
||||
dotPos = InStr(afterDash, ".")
|
||||
If dotPos > 0 Then
|
||||
params("gcwj") = Left(afterDash, dotPos - 1)
|
||||
Else
|
||||
params("gcwj") = afterDash
|
||||
End If
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取安装形式
|
||||
'
|
||||
' 输入: A0
|
||||
' 输出: A0
|
||||
'
|
||||
' 规则: 直接返回第一段的值
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Function ExtractAzxs(ByVal segment As String) As String
|
||||
ExtractAzxs = Trim(segment)
|
||||
End Function
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取表壳形式
|
||||
'
|
||||
' 输入: 531
|
||||
' 输出: 531
|
||||
'
|
||||
' 规则: 直接返回第二段的值
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Function ExtractBkxs(ByVal segment As String) As String
|
||||
ExtractBkxs = Trim(segment)
|
||||
End Function
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取过程连接和接液材质
|
||||
'
|
||||
' 输入: G123
|
||||
' 输出: gclj="G12", jycz="3"
|
||||
'
|
||||
' 规则:
|
||||
' - 过程连接: 去除最后一位
|
||||
' - 接液材质: 最后一位
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Sub ExtractGcljAndJycz(ByVal segment As String, ByRef gclj As String, ByRef jycz As String)
|
||||
segment = Trim(segment)
|
||||
|
||||
If Len(segment) >= 1 Then
|
||||
jycz = Right(segment, 1)
|
||||
If Len(segment) > 1 Then
|
||||
gclj = Left(segment, Len(segment) - 1)
|
||||
Else
|
||||
gclj = ""
|
||||
End If
|
||||
Else
|
||||
gclj = ""
|
||||
jycz = ""
|
||||
End If
|
||||
End Sub
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取量程范围
|
||||
'
|
||||
' 输入: M04
|
||||
' 输出: M04
|
||||
'
|
||||
' 规则: 直接返回第四段的值
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Function ExtractLcfw(ByVal segment As String) As String
|
||||
ExtractLcfw = Trim(segment)
|
||||
End Function
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 提取附加功能
|
||||
'
|
||||
' 输入: segments数组,起始索引为4
|
||||
' 输出: Y3 或 N1,N2 或 N1.N2
|
||||
'
|
||||
' 规则:
|
||||
' - 从第五段开始,所有段合并为附加功能
|
||||
' - 用逗号或点号分隔的多个功能,保留原分隔符
|
||||
' - 示例: Y3 -> Y3
|
||||
' - 示例: N1,N2.Y3 -> N1,N2,Y3
|
||||
' ------------------------------------------------------------------------------
|
||||
Private Function ExtractFjgn(ByRef segments As Variant, ByVal startIndex As Long) As String
|
||||
Dim result As String
|
||||
result = ""
|
||||
|
||||
Dim i As Long
|
||||
For i = startIndex To UBound(segments)
|
||||
Dim segment As String
|
||||
segment = Trim(CStr(segments(i)))
|
||||
|
||||
If Len(segment) > 0 Then
|
||||
' 替换点号为逗号(统一分隔符)
|
||||
segment = Replace(segment, MODEL_SEPARATOR_DOT, ",")
|
||||
|
||||
If Len(result) > 0 Then
|
||||
result = result & "," & segment
|
||||
Else
|
||||
result = segment
|
||||
End If
|
||||
End If
|
||||
Next i
|
||||
|
||||
ExtractFjgn = result
|
||||
End Function
|
||||
Reference in New Issue
Block a user