Compare commits
9 Commits
5c4f148c2a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0128c210f8 | ||
|
|
2d7ef5fc88 | ||
|
|
4bfbbc5b6e | ||
|
|
068ff5d465 | ||
|
|
0a8f3708e7 | ||
|
|
b73e39eeee | ||
|
|
d17d7c5864 | ||
|
|
76a75c9a05 | ||
|
|
f344cbe206 |
22
VBA/ClassModules/IModuleProcessor.cls
Normal file
22
VBA/ClassModules/IModuleProcessor.cls
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: IModuleProcessor
|
||||||
|
' 模块类别: 类模块 (用作接口 Interface)
|
||||||
|
' 模块职责: 定义统一的业务处理接口。此时的策略层已完全解耦,不关心过滤逻辑。
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' 方法名: ProcessSpec
|
||||||
|
' allBOMs: 总库集合 (主要用于克隆策略追加新行)
|
||||||
|
' targetBOMs: 由UI层(或中枢)精挑细选出来的目标行集合 (手术对象)
|
||||||
|
' paramName: 参数名(如 lcfw)
|
||||||
|
' newValue: 新规格代码(如 M19)
|
||||||
|
' engine: 条件注入引擎实例
|
||||||
|
Public Sub ProcessSpec( _
|
||||||
|
ByRef allBOMs As Collection, _
|
||||||
|
ByRef targetBOMs As Collection, _
|
||||||
|
ByVal paramName As String, _
|
||||||
|
ByVal newValue As String, _
|
||||||
|
ByRef engine As cConditionEngine)
|
||||||
|
|
||||||
|
' 接口中不写任何实现代码
|
||||||
|
End Sub
|
||||||
163
VBA/ClassModules/cBOMRow.cls
Normal file
163
VBA/ClassModules/cBOMRow.cls
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: cBOMRow
|
||||||
|
' 模块类别: 类模块 (Class Module)
|
||||||
|
' 所属项目: BOMForge
|
||||||
|
' 模块职责: 封装单行 BOM 数据,提供属性访问,并内置状态跟踪 (脏标记) 机制
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' 内部变量:映射 Excel 数据列
|
||||||
|
Private pExcelRowIndex As Long ' 记录该对象对应 Excel 表中的物理行号,保存时定位用
|
||||||
|
Private pRowNo As Long ' 行号 (如 10, 20)
|
||||||
|
Private pModuleType As String ' 模块 (如 表壳, 罩壳, 接头)
|
||||||
|
Private pCode As String ' 代号
|
||||||
|
Private pItemName As String ' 名称 (使用 ItemName 避免与内部 Name 关键字冲突)
|
||||||
|
Private pQuantity As Double ' 数量
|
||||||
|
Private pCondition As String ' 选择条件 (核心修改字段)
|
||||||
|
Private pRemark As String ' 备注
|
||||||
|
Private pCategory As String ' 类别
|
||||||
|
Private pParentCategory As String ' 上层类别
|
||||||
|
Private pCategoryCondition As String ' 类别选用条件
|
||||||
|
Private pCode66 As String ' 66代码
|
||||||
|
Private pBIPBaseRowNo As String ' BIP行号基数
|
||||||
|
|
||||||
|
' 内部变量:状态追踪
|
||||||
|
Private pIsDirty As Boolean ' 脏标记:True 表示对象数据被修改过,需要写回 Excel
|
||||||
|
|
||||||
|
' 初始化对象时,默认状态为干净 (未修改)
|
||||||
|
Private Sub Class_Initialize()
|
||||||
|
pIsDirty = False
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: ExcelRowIndex (物理行号)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get ExcelRowIndex() As Long: ExcelRowIndex = pExcelRowIndex: End Property
|
||||||
|
Public Property Let ExcelRowIndex(ByVal vNewValue As Long): pExcelRowIndex = vNewValue: End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: RowNo (BOM行号)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get RowNo() As Long: RowNo = pRowNo: End Property
|
||||||
|
Public Property Let RowNo(ByVal vNewValue As Long)
|
||||||
|
If pRowNo <> vNewValue Then pRowNo = vNewValue: pIsDirty = True
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: ModuleType (模块)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get ModuleType() As String: ModuleType = pModuleType: End Property
|
||||||
|
Public Property Let ModuleType(ByVal vNewValue As String)
|
||||||
|
If pModuleType <> vNewValue Then pModuleType = vNewValue: pIsDirty = True
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: Code (代号)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get Code() As String: Code = pCode: End Property
|
||||||
|
Public Property Let Code(ByVal vNewValue As String)
|
||||||
|
If pCode <> vNewValue Then pCode = vNewValue: pIsDirty = True
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: ItemName (名称)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get ItemName() As String: ItemName = pItemName: End Property
|
||||||
|
Public Property Let ItemName(ByVal vNewValue As String)
|
||||||
|
If pItemName <> vNewValue Then pItemName = vNewValue: pIsDirty = True
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: Quantity (数量)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get Quantity() As Double: Quantity = pQuantity: End Property
|
||||||
|
Public Property Let Quantity(ByVal vNewValue As Double)
|
||||||
|
If pQuantity <> vNewValue Then pQuantity = vNewValue: pIsDirty = True
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 属性: Condition (选择条件) - 这是本工具最核心要修改的字段
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get Condition() As String: Condition = pCondition: End Property
|
||||||
|
Public Property Let Condition(ByVal vNewValue As String)
|
||||||
|
' 只有当新写入的值和原有的值不一样时,才判定为被修改,打上脏标记
|
||||||
|
If pCondition <> vNewValue Then
|
||||||
|
pCondition = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 其他次要属性的 Get/Let
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get Remark() As String
|
||||||
|
Remark = pRemark
|
||||||
|
End Property
|
||||||
|
Public Property Let Remark(ByVal vNewValue As String)
|
||||||
|
If pRemark <> vNewValue Then
|
||||||
|
pRemark = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
Public Property Get Category() As String
|
||||||
|
Category = pCategory
|
||||||
|
End Property
|
||||||
|
Public Property Let Category(ByVal vNewValue As String)
|
||||||
|
If pCategory <> vNewValue Then
|
||||||
|
pCategory = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
Public Property Get ParentCategory() As String
|
||||||
|
ParentCategory = pParentCategory
|
||||||
|
End Property
|
||||||
|
Public Property Let ParentCategory(ByVal vNewValue As String)
|
||||||
|
If pParentCategory <> vNewValue Then
|
||||||
|
pParentCategory = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
Public Property Get CategoryCondition() As String
|
||||||
|
CategoryCondition = pCategoryCondition
|
||||||
|
End Property
|
||||||
|
Public Property Let CategoryCondition(ByVal vNewValue As String)
|
||||||
|
If pCategoryCondition <> vNewValue Then
|
||||||
|
pCategoryCondition = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
Public Property Get Code66() As String
|
||||||
|
Code66 = pCode66
|
||||||
|
End Property
|
||||||
|
Public Property Let Code66(ByVal vNewValue As String)
|
||||||
|
If pCode66 <> vNewValue Then
|
||||||
|
pCode66 = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
Public Property Get BIPBaseRowNo() As String
|
||||||
|
BIPBaseRowNo = pBIPBaseRowNo
|
||||||
|
End Property
|
||||||
|
Public Property Let BIPBaseRowNo(ByVal vNewValue As String)
|
||||||
|
If pBIPBaseRowNo <> vNewValue Then
|
||||||
|
pBIPBaseRowNo = vNewValue
|
||||||
|
pIsDirty = True
|
||||||
|
End If
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 状态追踪:只读的 IsDirty 和重置方法
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Property Get IsDirty() As Boolean
|
||||||
|
IsDirty = pIsDirty
|
||||||
|
End Property
|
||||||
|
|
||||||
|
' 可以在写回 Excel 后,调用此方法将状态重置为干净
|
||||||
|
Public Sub ResetDirtyFlag()
|
||||||
|
pIsDirty = False
|
||||||
|
End Sub
|
||||||
123
VBA/ClassModules/cConditionEngine.cls
Normal file
123
VBA/ClassModules/cConditionEngine.cls
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: cConditionEngine
|
||||||
|
' 模块类别: 类模块 (Class Module)
|
||||||
|
' 所属项目: BOMForge
|
||||||
|
' 模块职责: 负责安全的BOM条件字符串手术,解析并注入新的条件分支
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
Private regEx As Object
|
||||||
|
|
||||||
|
Private Sub Class_Initialize()
|
||||||
|
' 使用后期绑定创建正则表达式对象,无需手动在VBE中引用库,提高兼容性
|
||||||
|
Set regEx = CreateObject("VBScript.RegExp")
|
||||||
|
regEx.Global = True
|
||||||
|
regEx.IgnoreCase = True
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub Class_Terminate()
|
||||||
|
' 释放对象内存
|
||||||
|
Set regEx = Nothing
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 函数名称: InjectOr
|
||||||
|
' 函数功能: 将新参数作为 OR 条件安全地注入到现有的逻辑表达式中
|
||||||
|
' 参数说明:
|
||||||
|
' - expression: 原始的条件字符串,例如 "lcfw=M01 AND gclj!=M20"
|
||||||
|
' - paramName: 要注入的参数名,例如 "lcfw"
|
||||||
|
' - newValue: 要注入的新值,例如 "M19"
|
||||||
|
' 返回值: 注入后的新字符串
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Function InjectOr(ByVal expression As String, ByVal paramName As String, ByVal newValue As String) As String
|
||||||
|
Dim targetCondition As String
|
||||||
|
targetCondition = paramName & "=" & newValue
|
||||||
|
|
||||||
|
' [防御机制 1]:如果原表达式中已经存在我们要注入的完整条件,直接返回原字符串
|
||||||
|
If InStr(1, expression, targetCondition, vbTextCompare) > 0 Then
|
||||||
|
InjectOr = expression
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' [防御机制 2]:如果原表达式中根本不存在这个参数(如全是azxs),则忽略(业务上一般只在同类规格上追加)
|
||||||
|
If InStr(1, expression, paramName & "=", vbTextCompare) = 0 And _
|
||||||
|
InStr(1, expression, paramName & " =", vbTextCompare) = 0 Then
|
||||||
|
InjectOr = expression
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' ==========================================================================
|
||||||
|
' 场景 A:参数存在于括号内部
|
||||||
|
' 示例:(lcfw=M12 OR lcfw=M13) AND gclj!=M20
|
||||||
|
' 动作:找到括号,并在右括号前插入 " OR lcfw=M19"
|
||||||
|
' ==========================================================================
|
||||||
|
' 正则解释: 匹配左括号,中间不包含右括号,且包含目标参数=的片段,直到右括号
|
||||||
|
regEx.Pattern = "\([^)]*\b" & paramName & "\b\s*=[^)]*\)"
|
||||||
|
|
||||||
|
If regEx.Test(expression) Then
|
||||||
|
Dim matchBlocks As Object
|
||||||
|
Dim matchBlock As Object
|
||||||
|
Set matchBlocks = regEx.Execute(expression)
|
||||||
|
|
||||||
|
Dim resultExpr As String
|
||||||
|
resultExpr = expression
|
||||||
|
|
||||||
|
' 遍历所有匹配的括号块并替换(万一存在多个括号里都有lcfw)
|
||||||
|
For Each matchBlock In matchBlocks
|
||||||
|
Dim oldBlockStr As String
|
||||||
|
oldBlockStr = matchBlock.value
|
||||||
|
|
||||||
|
Dim newBlockStr As String
|
||||||
|
' 剥离最后的右括号,加上我们要追加的条件,再把右括号补回去
|
||||||
|
newBlockStr = Left(oldBlockStr, Len(oldBlockStr) - 1) & " OR " & targetCondition & ")"
|
||||||
|
|
||||||
|
' 在主字符串中执行替换
|
||||||
|
resultExpr = Replace(resultExpr, oldBlockStr, newBlockStr)
|
||||||
|
Next matchBlock
|
||||||
|
|
||||||
|
InjectOr = resultExpr
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' ==========================================================================
|
||||||
|
' 场景 B:参数在括号外部,且表达式中存在 AND 逻辑
|
||||||
|
' 示例:lcfw=M01 AND gclj!=M20
|
||||||
|
' 动作:必须加括号包裹它,变成 (lcfw=M01 OR lcfw=M19) AND gclj!=M20
|
||||||
|
' ==========================================================================
|
||||||
|
If InStr(1, expression, "AND", vbTextCompare) > 0 Then
|
||||||
|
' 正则解释:匹配单独的参数表达式,如 lcfw=M01 (允许等号两边有空格)
|
||||||
|
regEx.Pattern = "\b" & paramName & "\s*=\s*[A-Za-z0-9_]+"
|
||||||
|
|
||||||
|
If regEx.Test(expression) Then
|
||||||
|
Dim standaloneMatches As Object
|
||||||
|
Dim sMatch As Object
|
||||||
|
Set standaloneMatches = regEx.Execute(expression)
|
||||||
|
|
||||||
|
Dim resultStandalone As String
|
||||||
|
resultStandalone = expression
|
||||||
|
|
||||||
|
For Each sMatch In standaloneMatches
|
||||||
|
Dim oldStandaloneStr As String
|
||||||
|
oldStandaloneStr = sMatch.value
|
||||||
|
|
||||||
|
Dim newStandaloneStr As String
|
||||||
|
' 将原来独立的条件包裹进括号,并追加 OR 逻辑
|
||||||
|
newStandaloneStr = "(" & oldStandaloneStr & " OR " & targetCondition & ")"
|
||||||
|
|
||||||
|
resultStandalone = Replace(resultStandalone, oldStandaloneStr, newStandaloneStr)
|
||||||
|
Next sMatch
|
||||||
|
|
||||||
|
InjectOr = resultStandalone
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
|
||||||
|
' ==========================================================================
|
||||||
|
' 场景 C:没有任何括号,也没有 AND 逻辑,纯 OR 链
|
||||||
|
' 示例:lcfw=M01 OR lcfw=M02 OR lcfw=M03 (如盘止钉)
|
||||||
|
' 动作:直接在字符串最末尾追加即可
|
||||||
|
' ==========================================================================
|
||||||
|
expression = Trim(expression)
|
||||||
|
InjectOr = expression & " OR " & targetCondition
|
||||||
|
|
||||||
|
End Function
|
||||||
24
VBA/ClassModules/cDynamicEvent.cls
Normal file
24
VBA/ClassModules/cDynamicEvent.cls
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: cDynamicEvent
|
||||||
|
' 模块类别: 类模块 (Class Module)
|
||||||
|
' 模块职责: 捕获在 Frame 中动态生成的 CheckBox 的点击事件,并触发主窗体的刷新
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' 声明一个带有事件的复选框对象
|
||||||
|
Public WithEvents dynCheckBox As MSForms.CheckBox
|
||||||
|
|
||||||
|
' 指向主窗体的引用,用于调用主窗体的方法
|
||||||
|
Public parentForm As Object
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 当动态生成的复选框被点击时,触发此事件
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Private Sub dynCheckBox_Click()
|
||||||
|
' 如果父窗体存在,且用户勾选了"显示具体物料",则通知父窗体刷新下方列表
|
||||||
|
If Not parentForm Is Nothing Then
|
||||||
|
If parentForm.chkShowDetails.value = True Then
|
||||||
|
parentForm.RefreshDetailsPanel
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
24
VBA/ClassModules/cProcessor_AppendOnly.cls
Normal file
24
VBA/ClassModules/cProcessor_AppendOnly.cls
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: cProcessor_AppendOnly
|
||||||
|
' 模块类别: 类模块
|
||||||
|
' 模块职责: 通用追加策略。
|
||||||
|
' 逻辑描述: 无脑遍历传入的 targetBOMs 集合,利用引擎进行条件追加。
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
Implements IModuleProcessor
|
||||||
|
|
||||||
|
Private Sub IModuleProcessor_ProcessSpec( _
|
||||||
|
ByRef allBOMs As Collection, _
|
||||||
|
ByRef targetBOMs As Collection, _
|
||||||
|
ByVal paramName As String, _
|
||||||
|
ByVal newValue As String, _
|
||||||
|
ByRef engine As cConditionEngine)
|
||||||
|
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
|
||||||
|
' 不再需要判断名字,因为 UI 层传过来的 targetBOMs 就是已经挑好的
|
||||||
|
For Each rowObj In targetBOMs
|
||||||
|
rowObj.Condition = engine.InjectOr(rowObj.Condition, paramName, newValue)
|
||||||
|
Next rowObj
|
||||||
|
|
||||||
|
End Sub
|
||||||
29
VBA/ClassModules/cProcessor_CloneNew.cls
Normal file
29
VBA/ClassModules/cProcessor_CloneNew.cls
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: cProcessor_CloneNew
|
||||||
|
' 模块类别: 类模块
|
||||||
|
' 模块职责: 通用克隆新增策略。
|
||||||
|
' 逻辑描述: 遍历传入的 targetBOMs 母版集合,每遇到一个就克隆一行追加到总库,并赋新值。
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
Implements IModuleProcessor
|
||||||
|
|
||||||
|
Private Sub IModuleProcessor_ProcessSpec( _
|
||||||
|
ByRef allBOMs As Collection, _
|
||||||
|
ByRef targetBOMs As Collection, _
|
||||||
|
ByVal paramName As String, _
|
||||||
|
ByVal newValue As String, _
|
||||||
|
ByRef engine As cConditionEngine)
|
||||||
|
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
Dim newRow As cBOMRow
|
||||||
|
|
||||||
|
' 为 targetBOMs 里的每一个母版克隆出一个新对象
|
||||||
|
For Each rowObj In targetBOMs
|
||||||
|
' 调用数据访问层的克隆方法,自动加入 allBOMs 总集合
|
||||||
|
Set newRow = mBOMRepository.InsertNewBOMRow(allBOMs, rowObj)
|
||||||
|
|
||||||
|
' 新行的选择条件直接赋予纯粹的新规格 (例如:lcfw=M19)
|
||||||
|
newRow.Condition = paramName & "=" & newValue
|
||||||
|
Next rowObj
|
||||||
|
|
||||||
|
End Sub
|
||||||
355
VBA/Forms/frmAddSpec.frm
Normal file
355
VBA/Forms/frmAddSpec.frm
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: frmAddSpec (代码后置)
|
||||||
|
' 模块类别: 用户窗体代码 (UserForm Code)
|
||||||
|
' 模块职责: 处理 UI 交互、数据展示联动、组装精准目标集合并调用主控中枢
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' --- 模块级变量 ---
|
||||||
|
Private mGlobalBOMs As Collection ' 内存中的 BOM 总库
|
||||||
|
Private mDynamicCheckboxes As Collection ' 保存动态生成的复选框和事件对象
|
||||||
|
Private mTargetWorksheet As Worksheet ' 目标工作表 (平台配置清单)
|
||||||
|
Private mParamWorksheet As Worksheet ' 参数配置工作表 (新增)
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 1. 窗体初始化 (加载数据、动态生成控件)
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub UserForm_Initialize()
|
||||||
|
' 尝试绑定数据源工作表
|
||||||
|
On Error Resume Next
|
||||||
|
Set mTargetWorksheet = ThisWorkbook.Worksheets("平台配置清单")
|
||||||
|
Set mParamWorksheet = ThisWorkbook.Worksheets("参数配置") ' 绑定参数表
|
||||||
|
On Error GoTo 0
|
||||||
|
|
||||||
|
If mTargetWorksheet Is Nothing Then
|
||||||
|
MsgBox "未找到名为 [平台配置清单] 的工作表,请检查!", vbCritical
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
If mParamWorksheet Is Nothing Then
|
||||||
|
MsgBox "未找到名为 [参数配置] 的工作表,无法加载物料名称列表!", vbCritical
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 初始化策略下拉框 (中文化显示,底层输出英文指令)
|
||||||
|
With Me.cboStrategy
|
||||||
|
.Clear
|
||||||
|
.ColumnCount = 2
|
||||||
|
.ColumnWidths = "180;0" ' 第一列中文可见,第二列英文标识隐藏
|
||||||
|
.BoundColumn = 2 ' 指定 .Value 属性读取隐藏的第二列
|
||||||
|
|
||||||
|
.AddItem "追加规格 (在原有条件上追加)"
|
||||||
|
.List(0, 1) = "APPEND"
|
||||||
|
|
||||||
|
.AddItem "克隆新增 (复制母版并生成新行)"
|
||||||
|
.List(1, 1) = "CLONE"
|
||||||
|
|
||||||
|
.ListIndex = 0 ' 默认选中 APPEND
|
||||||
|
End With
|
||||||
|
|
||||||
|
' 初始化明细列表框 (隐藏首列 ExcelRowIndex 用于唯一映射)
|
||||||
|
With Me.lstDetails
|
||||||
|
.ColumnCount = 4
|
||||||
|
.ColumnWidths = "0;60;100;150" ' 第0列宽度为0(隐藏),1列代号,2列名称,3列条件
|
||||||
|
.MultiSelect = fmMultiSelectMulti
|
||||||
|
.ListStyle = fmListStyleOption
|
||||||
|
End With
|
||||||
|
|
||||||
|
' 初始化参数名称下拉框 (新增)
|
||||||
|
With Me.cboParamName
|
||||||
|
.Clear
|
||||||
|
If Not mParamWorksheet Is Nothing Then
|
||||||
|
Dim paramLastRow As Long
|
||||||
|
Dim j As Long
|
||||||
|
Dim paramStr As String
|
||||||
|
|
||||||
|
' 表头在第2行,数据在C列,实际数据从第3行开始
|
||||||
|
paramLastRow = mParamWorksheet.Cells(mParamWorksheet.Rows.count, "C").End(xlUp).row
|
||||||
|
If paramLastRow >= 3 Then
|
||||||
|
For j = 3 To paramLastRow
|
||||||
|
paramStr = Trim(mParamWorksheet.Cells(j, 3).value)
|
||||||
|
If paramStr <> "" Then
|
||||||
|
.AddItem paramStr
|
||||||
|
End If
|
||||||
|
Next j
|
||||||
|
|
||||||
|
' 将数据中的第一个数据作为默认数据
|
||||||
|
If .ListCount > 0 Then .ListIndex = 0
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
End With
|
||||||
|
|
||||||
|
' 初始化预览文本框
|
||||||
|
On Error Resume Next
|
||||||
|
Me.txtConditionPreview.Text = ""
|
||||||
|
On Error GoTo 0
|
||||||
|
|
||||||
|
' 清理提示信息
|
||||||
|
Me.lblLog.Caption = ""
|
||||||
|
|
||||||
|
' 核心:加载数据并渲染动态复选框
|
||||||
|
LoadDataAndRender
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 2. 核心加载逻辑与动态渲染
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub LoadDataAndRender()
|
||||||
|
' 通过主控大脑加载所有数据 (用于明细刷新和执行手术)
|
||||||
|
Set mGlobalBOMs = mSpecAdditionManager.LoadData(mTargetWorksheet)
|
||||||
|
|
||||||
|
' 从 [参数配置] 工作表获取目标物料名称 (新逻辑)
|
||||||
|
Dim dictNames As Object
|
||||||
|
Set dictNames = CreateObject("Scripting.Dictionary")
|
||||||
|
|
||||||
|
Dim lastRow As Long
|
||||||
|
Dim i As Long
|
||||||
|
Dim itemNameStr As String
|
||||||
|
|
||||||
|
' 表头在第2行,数据在A列,实际数据从第3行开始
|
||||||
|
If Not mParamWorksheet Is Nothing Then
|
||||||
|
lastRow = mParamWorksheet.Cells(mParamWorksheet.Rows.count, "A").End(xlUp).row
|
||||||
|
If lastRow >= 3 Then
|
||||||
|
For i = 3 To lastRow
|
||||||
|
itemNameStr = Trim(mParamWorksheet.Cells(i, 1).value)
|
||||||
|
If itemNameStr <> "" Then
|
||||||
|
If Not dictNames.Exists(itemNameStr) Then
|
||||||
|
dictNames.Add itemNameStr, 1 ' 顺便利用字典去重
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
Next i
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 清空已有的动态控件
|
||||||
|
Dim ctrl As Control
|
||||||
|
For Each ctrl In Me.fraItemNames.Controls
|
||||||
|
Me.fraItemNames.Controls.Remove ctrl.Name
|
||||||
|
Next ctrl
|
||||||
|
Set mDynamicCheckboxes = New Collection
|
||||||
|
|
||||||
|
' 在 Frame 中动态生成 CheckBox
|
||||||
|
Dim key As Variant
|
||||||
|
Dim chk As MSForms.CheckBox
|
||||||
|
Dim ev As cDynamicEvent
|
||||||
|
Dim topPos As Single, leftPos As Single
|
||||||
|
Dim itemIndex As Integer
|
||||||
|
|
||||||
|
topPos = 10
|
||||||
|
leftPos = 10
|
||||||
|
itemIndex = 1
|
||||||
|
|
||||||
|
For Each key In dictNames.Keys
|
||||||
|
Set chk = Me.fraItemNames.Controls.Add("Forms.CheckBox.1", "chkDynItem_" & itemIndex, True)
|
||||||
|
chk.Caption = CStr(key)
|
||||||
|
chk.Top = topPos
|
||||||
|
chk.Left = leftPos
|
||||||
|
chk.Width = 100
|
||||||
|
chk.Height = 15
|
||||||
|
|
||||||
|
' 简单的流式布局:超出宽度则换行
|
||||||
|
leftPos = leftPos + 110
|
||||||
|
If leftPos + 100 > Me.fraItemNames.InsideWidth Then
|
||||||
|
leftPos = 10
|
||||||
|
topPos = topPos + 20
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 将动态 CheckBox 绑定到自定义事件类中,以便捕获点击
|
||||||
|
Set ev = New cDynamicEvent
|
||||||
|
Set ev.dynCheckBox = chk
|
||||||
|
Set ev.parentForm = Me
|
||||||
|
mDynamicCheckboxes.Add ev
|
||||||
|
|
||||||
|
itemIndex = itemIndex + 1
|
||||||
|
Next key
|
||||||
|
|
||||||
|
' 设置 Frame 滚动条属性
|
||||||
|
Me.fraItemNames.ScrollBars = fmScrollBarsVertical
|
||||||
|
Me.fraItemNames.ScrollHeight = topPos + 30
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 3. 界面联动交互逻辑
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub cboStrategy_Change()
|
||||||
|
' 策略变更联动:只有 APPEND 策略才允许使用细粒度列表
|
||||||
|
If Me.cboStrategy.value = "CLONE" Then
|
||||||
|
Me.chkShowDetails.value = False
|
||||||
|
Me.chkShowDetails.Enabled = False
|
||||||
|
Else
|
||||||
|
Me.chkShowDetails.Enabled = True
|
||||||
|
End If
|
||||||
|
RefreshDetailsPanel
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub chkShowDetails_Click()
|
||||||
|
RefreshDetailsPanel
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub chkSelectAllDetails_Click()
|
||||||
|
Dim i As Integer
|
||||||
|
For i = 0 To Me.lstDetails.ListCount - 1
|
||||||
|
Me.lstDetails.Selected(i) = Me.chkSelectAllDetails.value
|
||||||
|
Next i
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' 供 cDynamicEvent 调用的公开刷新方法
|
||||||
|
Public Sub RefreshDetailsPanel()
|
||||||
|
Me.lstDetails.Clear
|
||||||
|
Me.chkSelectAllDetails.value = False
|
||||||
|
|
||||||
|
' 刷新列表时顺便清空预览框
|
||||||
|
On Error Resume Next
|
||||||
|
Me.txtConditionPreview.Text = ""
|
||||||
|
On Error GoTo 0
|
||||||
|
|
||||||
|
If Not Me.chkShowDetails.value Then Exit Sub ' 未开启显示明细则跳过
|
||||||
|
|
||||||
|
' 收集目前被勾选的宏观名称
|
||||||
|
Dim checkedNames As Object
|
||||||
|
Set checkedNames = CreateObject("Scripting.Dictionary")
|
||||||
|
|
||||||
|
Dim ev As cDynamicEvent
|
||||||
|
For Each ev In mDynamicCheckboxes
|
||||||
|
If ev.dynCheckBox.value = True Then
|
||||||
|
checkedNames.Add ev.dynCheckBox.Caption, 1
|
||||||
|
End If
|
||||||
|
Next ev
|
||||||
|
|
||||||
|
If checkedNames.count = 0 Then Exit Sub
|
||||||
|
|
||||||
|
' 遍历总库,把符合勾选名称的物料填充到 ListBox
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
For Each rowObj In mGlobalBOMs
|
||||||
|
If checkedNames.Exists(rowObj.ItemName) Then
|
||||||
|
Me.lstDetails.AddItem rowObj.ExcelRowIndex ' 第 0 列隐藏,存物理行号作为唯一ID
|
||||||
|
Me.lstDetails.List(Me.lstDetails.ListCount - 1, 1) = rowObj.Code
|
||||||
|
Me.lstDetails.List(Me.lstDetails.ListCount - 1, 2) = rowObj.ItemName
|
||||||
|
Me.lstDetails.List(Me.lstDetails.ListCount - 1, 3) = rowObj.Condition
|
||||||
|
End If
|
||||||
|
Next rowObj
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 新增:处理列表框点击事件,将超长的选择条件发送到预览框自动换行显示
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub lstDetails_Change()
|
||||||
|
' 在 MultiSelect 模式下,Click 事件不生效,必须使用 Change 事件
|
||||||
|
' ListIndex 代表当前具有虚线焦点框的行(即刚刚被点击的行)
|
||||||
|
If Me.lstDetails.ListIndex >= 0 Then
|
||||||
|
On Error Resume Next
|
||||||
|
' 获取隐藏列中的物理行号
|
||||||
|
Dim targetId As Long
|
||||||
|
targetId = CLng(Me.lstDetails.List(Me.lstDetails.ListIndex, 0))
|
||||||
|
|
||||||
|
' 去内存库里抓取完整的 Condition,赋值给预览框
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
For Each rowObj In mGlobalBOMs
|
||||||
|
If rowObj.ExcelRowIndex = targetId Then
|
||||||
|
Me.txtConditionPreview.Text = rowObj.Condition
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next rowObj
|
||||||
|
On Error GoTo 0
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 4. 执行核心流水线
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub btnExecute_Click()
|
||||||
|
' --- 校验输入 ---
|
||||||
|
Dim paramName As String, newValue As String, strategy As String, strategyName As String
|
||||||
|
paramName = Trim(Me.cboParamName.Text)
|
||||||
|
newValue = Trim(Me.txtNewValue.Text)
|
||||||
|
strategy = Me.cboStrategy.value ' 获取隐藏的底层代码 (APPEND / CLONE)
|
||||||
|
strategyName = Me.cboStrategy.Text ' 获取界面显示的中文名称
|
||||||
|
|
||||||
|
If paramName = "" Or newValue = "" Then
|
||||||
|
MsgBox "参数名称和新增规格值不能为空!", vbExclamation
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 收集在宏观 Frame 中被勾选的名称
|
||||||
|
Dim checkedNames As Object
|
||||||
|
Set checkedNames = CreateObject("Scripting.Dictionary")
|
||||||
|
Dim ev As cDynamicEvent
|
||||||
|
For Each ev In mDynamicCheckboxes
|
||||||
|
If ev.dynCheckBox.value = True Then checkedNames.Add ev.dynCheckBox.Caption, 1
|
||||||
|
Next ev
|
||||||
|
|
||||||
|
If checkedNames.count = 0 Then
|
||||||
|
MsgBox "请至少勾选一个目标物料名称!", vbExclamation
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
' --- 组装精准的 targetBOMs 集合 ---
|
||||||
|
Dim targetBOMs As New Collection
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
Dim i As Integer
|
||||||
|
Dim actionLog As String
|
||||||
|
|
||||||
|
If Me.chkShowDetails.value = True Then
|
||||||
|
' 【细粒度模式】:只收集 ListBox 中打钩的明细行
|
||||||
|
Dim hasDetailChecked As Boolean
|
||||||
|
hasDetailChecked = False
|
||||||
|
|
||||||
|
For i = 0 To Me.lstDetails.ListCount - 1
|
||||||
|
If Me.lstDetails.Selected(i) = True Then
|
||||||
|
hasDetailChecked = True
|
||||||
|
Dim targetId As Long
|
||||||
|
targetId = CLng(Me.lstDetails.List(i, 0)) ' 取出隐藏的物理行号
|
||||||
|
|
||||||
|
' 在总库中找到对应的对象并塞入目标集合
|
||||||
|
For Each rowObj In mGlobalBOMs
|
||||||
|
If rowObj.ExcelRowIndex = targetId Then
|
||||||
|
targetBOMs.Add rowObj
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next rowObj
|
||||||
|
End If
|
||||||
|
Next i
|
||||||
|
|
||||||
|
If Not hasDetailChecked Then
|
||||||
|
MsgBox "开启了细粒度筛选,请至少在下方列表中勾选一条物料!", vbExclamation
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
actionLog = "细粒度模式更新了 " & targetBOMs.count & " 条特定物料。"
|
||||||
|
|
||||||
|
Else
|
||||||
|
' 【宏观模式】:收集所有符合打钩名称的行
|
||||||
|
For Each rowObj In mGlobalBOMs
|
||||||
|
If checkedNames.Exists(rowObj.ItemName) Then
|
||||||
|
targetBOMs.Add rowObj
|
||||||
|
End If
|
||||||
|
Next rowObj
|
||||||
|
actionLog = "宏观模式扫描了 " & targetBOMs.count & " 条变种物料。"
|
||||||
|
End If
|
||||||
|
|
||||||
|
' --- 调用调度中枢进行外科手术 ---
|
||||||
|
Me.btnExecute.Caption = "正在处理..."
|
||||||
|
Me.btnExecute.Enabled = False
|
||||||
|
DoEvents ' 刷新UI防止假死
|
||||||
|
|
||||||
|
' 执行!
|
||||||
|
mSpecAdditionManager.ExecutePipeline mTargetWorksheet, mGlobalBOMs, targetBOMs, paramName, newValue, strategy
|
||||||
|
|
||||||
|
' --- 完成与恢复 ---
|
||||||
|
Me.btnExecute.Caption = "保存入库 (执行)"
|
||||||
|
Me.btnExecute.Enabled = True
|
||||||
|
Me.txtNewValue.Text = ""
|
||||||
|
|
||||||
|
Me.lblLog.Caption = "成功![" & newValue & "] 规格已通过 [" & strategyName & "] 处理完成。" & vbCrLf & actionLog
|
||||||
|
Me.lblLog.ForeColor = RGB(0, 128, 0) ' 绿色
|
||||||
|
|
||||||
|
' 重新加载数据刷新UI
|
||||||
|
LoadDataAndRender
|
||||||
|
RefreshDetailsPanel
|
||||||
|
|
||||||
|
MsgBox "规格新增完成!请查看表格确认结果。", vbInformation
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' 关闭按钮
|
||||||
|
Private Sub btnClose_Click()
|
||||||
|
Unload Me
|
||||||
|
End Sub
|
||||||
127
VBA/Modules/mBOMRepository.bas
Normal file
127
VBA/Modules/mBOMRepository.bas
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: mBOMRepository
|
||||||
|
' 模块类别: 标准模块 (Standard Module)
|
||||||
|
' 所属项目: BOMForge
|
||||||
|
' 模块职责: 负责 Excel 表格与 cBOMRow 内存对象之间的双向数据交互
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 函数名称: LoadAllBOMs
|
||||||
|
' 函数功能: 将工作表中的 BOM 数据逐行加载为 cBOMRow 对象的集合
|
||||||
|
' 参数说明: ws - 目标工作表对象
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Function LoadAllBOMs(ws As Worksheet) As Collection
|
||||||
|
Dim coll As New Collection
|
||||||
|
Dim lastRow As Long
|
||||||
|
Dim i As Long
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
|
||||||
|
' 获取 A 列最后一行 (假设 A 列总是有数据的,如行号)
|
||||||
|
lastRow = ws.Cells(ws.Rows.count, "A").End(xlUp).row
|
||||||
|
|
||||||
|
' 根据业务说明,表头在第3行,数据从第4行开始
|
||||||
|
If lastRow < 4 Then
|
||||||
|
Set LoadAllBOMs = coll
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
For i = 4 To lastRow
|
||||||
|
Set rowObj = New cBOMRow
|
||||||
|
|
||||||
|
' 映射 Excel 列到对象属性
|
||||||
|
rowObj.ExcelRowIndex = i
|
||||||
|
rowObj.RowNo = Val(ws.Cells(i, 1).value)
|
||||||
|
rowObj.ModuleType = Trim(ws.Cells(i, 2).value)
|
||||||
|
rowObj.Code = Trim(ws.Cells(i, 3).value)
|
||||||
|
rowObj.ItemName = Trim(ws.Cells(i, 4).value)
|
||||||
|
rowObj.Quantity = Val(ws.Cells(i, 5).value)
|
||||||
|
rowObj.Condition = Trim(ws.Cells(i, 6).value)
|
||||||
|
rowObj.Remark = Trim(ws.Cells(i, 7).value)
|
||||||
|
rowObj.Category = Trim(ws.Cells(i, 8).value)
|
||||||
|
rowObj.ParentCategory = Trim(ws.Cells(i, 9).value)
|
||||||
|
rowObj.CategoryCondition = Trim(ws.Cells(i, 10).value)
|
||||||
|
rowObj.Code66 = Trim(ws.Cells(i, 11).value)
|
||||||
|
rowObj.BIPBaseRowNo = Trim(ws.Cells(i, 12).value)
|
||||||
|
|
||||||
|
' 【关键】刚从 Excel 读取的数据是原生态的,重置脏标记
|
||||||
|
rowObj.ResetDirtyFlag
|
||||||
|
|
||||||
|
coll.Add rowObj
|
||||||
|
Next i
|
||||||
|
|
||||||
|
Set LoadAllBOMs = coll
|
||||||
|
End Function
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 函数名称: SaveAll
|
||||||
|
' 函数功能: 遍历集合,仅将发生改变 (IsDirty = True) 的对象写回 Excel
|
||||||
|
' 参数说明: ws - 目标工作表对象, bomCollection - 内存对象集合
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Sub SaveAll(ws As Worksheet, bomCollection As Collection)
|
||||||
|
Dim rowObj As cBOMRow
|
||||||
|
Dim r As Long
|
||||||
|
|
||||||
|
For Each rowObj In bomCollection
|
||||||
|
' 【性能核心】只写回被业务策略修改过的行
|
||||||
|
If rowObj.IsDirty Then
|
||||||
|
r = rowObj.ExcelRowIndex
|
||||||
|
|
||||||
|
' 如果是新增行,ExcelRowIndex 可能为空或需要重新计算
|
||||||
|
If r <= 0 Then
|
||||||
|
r = ws.Cells(ws.Rows.count, "A").End(xlUp).row + 1
|
||||||
|
rowObj.ExcelRowIndex = r
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 将属性写回对应的列
|
||||||
|
ws.Cells(r, 1).value = rowObj.RowNo
|
||||||
|
ws.Cells(r, 2).value = rowObj.ModuleType
|
||||||
|
ws.Cells(r, 3).value = rowObj.Code
|
||||||
|
ws.Cells(r, 4).value = rowObj.ItemName
|
||||||
|
ws.Cells(r, 5).value = rowObj.Quantity
|
||||||
|
ws.Cells(r, 6).value = rowObj.Condition
|
||||||
|
ws.Cells(r, 7).value = rowObj.Remark
|
||||||
|
ws.Cells(r, 8).value = rowObj.Category
|
||||||
|
ws.Cells(r, 9).value = rowObj.ParentCategory
|
||||||
|
ws.Cells(r, 10).value = rowObj.CategoryCondition
|
||||||
|
ws.Cells(r, 11).value = rowObj.Code66
|
||||||
|
ws.Cells(r, 12).value = rowObj.BIPBaseRowNo
|
||||||
|
|
||||||
|
' 保存完毕,重置脏标记
|
||||||
|
rowObj.ResetDirtyFlag
|
||||||
|
End If
|
||||||
|
Next rowObj
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 函数名称: InsertNewBOMRow
|
||||||
|
' 函数功能: 基于现有行克隆出一个新行对象,用于弹性元件等需要全新规格新增的场景
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Function InsertNewBOMRow(bomCollection As Collection, copyFromObj As cBOMRow) As cBOMRow
|
||||||
|
Dim newObj As New cBOMRow
|
||||||
|
|
||||||
|
' 设置为 0 意味着在 SaveAll 时,它会自动追加到 Excel 的最后一行
|
||||||
|
newObj.ExcelRowIndex = 0
|
||||||
|
|
||||||
|
' 属性克隆
|
||||||
|
newObj.RowNo = copyFromObj.RowNo ' 这里的行号往往需要外部策略重新编排,先克隆
|
||||||
|
newObj.ModuleType = copyFromObj.ModuleType
|
||||||
|
newObj.Code = copyFromObj.Code ' 同样等待外部策略分配新代码
|
||||||
|
newObj.ItemName = copyFromObj.ItemName
|
||||||
|
newObj.Quantity = copyFromObj.Quantity
|
||||||
|
newObj.Condition = copyFromObj.Condition
|
||||||
|
newObj.Remark = copyFromObj.Remark
|
||||||
|
newObj.Category = copyFromObj.Category
|
||||||
|
newObj.ParentCategory = copyFromObj.ParentCategory
|
||||||
|
newObj.CategoryCondition = copyFromObj.CategoryCondition
|
||||||
|
newObj.Code66 = copyFromObj.Code66
|
||||||
|
newObj.BIPBaseRowNo = copyFromObj.BIPBaseRowNo
|
||||||
|
|
||||||
|
' 强制触发一下脏标记,确保这个新对象会被 SaveAll 捕获写进 Excel
|
||||||
|
' 由于 Let 属性判断 <> 才会触发,我们采用一个小技巧
|
||||||
|
newObj.Code = newObj.Code & "_TEMP"
|
||||||
|
newObj.Code = copyFromObj.Code
|
||||||
|
|
||||||
|
bomCollection.Add newObj
|
||||||
|
Set InsertNewBOMRow = newObj
|
||||||
|
End Function
|
||||||
27
VBA/Modules/mProcessorFactory.bas
Normal file
27
VBA/Modules/mProcessorFactory.bas
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: mProcessorFactory
|
||||||
|
' 模块类别: 标准模块 (Standard Module)
|
||||||
|
' 模块职责: 负责根据传入的策略类型标识,实例化并返回对应的业务策略对象
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 函数名称: GetProcessor
|
||||||
|
' 函数功能: 根据策略别名,返回对应的 IModuleProcessor 实现类
|
||||||
|
' 参数说明: strategyType - 策略标识 (例如 "APPEND" 或 "CLONE")
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Function GetProcessor(ByVal strategyType As String) As IModuleProcessor
|
||||||
|
Select Case UCase(Trim(strategyType))
|
||||||
|
Case "APPEND"
|
||||||
|
' 返回追加策略实例
|
||||||
|
Set GetProcessor = New cProcessor_AppendOnly
|
||||||
|
|
||||||
|
Case "CLONE"
|
||||||
|
' 返回克隆新增策略实例
|
||||||
|
Set GetProcessor = New cProcessor_CloneNew
|
||||||
|
|
||||||
|
Case Else
|
||||||
|
' 如果传入了未知的策略,主动抛出异常阻断运行
|
||||||
|
Err.Raise vbObjectError + 513, "ProcessorFactory", "未知的业务处理策略: " & strategyType
|
||||||
|
End Select
|
||||||
|
End Function
|
||||||
58
VBA/Modules/mSpecAdditionManager.bas
Normal file
58
VBA/Modules/mSpecAdditionManager.bas
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: mSpecAdditionManager
|
||||||
|
' 模块类别: 标准模块 (Standard Module)
|
||||||
|
' 模块职责: 整个架构的主控大脑,负责协调各层组件,完成端到端的数据处理流水线
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 过程名称: LoadData
|
||||||
|
' 过程功能: 供 UI 初始化时调用,加载总库数据
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Function LoadData(ByVal ws As Worksheet) As Collection
|
||||||
|
Set LoadData = mBOMRepository.LoadAllBOMs(ws)
|
||||||
|
End Function
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 过程名称: ExecutePipeline
|
||||||
|
' 过程功能: 执行完整的新增规格流水线
|
||||||
|
' 参数说明:
|
||||||
|
' ws - 目标工作表对象 (保存时需要)
|
||||||
|
' allBOMs - 内存中的总库集合
|
||||||
|
' targetBOMs - UI层过滤组装好的【精准目标集合】
|
||||||
|
' paramName - 要修改的参数名 (如 "lcfw")
|
||||||
|
' newValue - 新增的规格值 (如 "M19")
|
||||||
|
' strategyType - UI选择的执行策略 (如 "APPEND" 或 "CLONE")
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Sub ExecutePipeline( _
|
||||||
|
ByVal ws As Worksheet, _
|
||||||
|
ByRef allBOMs As Collection, _
|
||||||
|
ByRef targetBOMs As Collection, _
|
||||||
|
ByVal paramName As String, _
|
||||||
|
ByVal newValue As String, _
|
||||||
|
ByVal strategyType As String)
|
||||||
|
|
||||||
|
' 如果没有选中任何目标,直接中断退出
|
||||||
|
If targetBOMs Is Nothing Then Exit Sub
|
||||||
|
If targetBOMs.count = 0 Then Exit Sub
|
||||||
|
|
||||||
|
Dim engine As cConditionEngine
|
||||||
|
Dim processor As IModuleProcessor
|
||||||
|
|
||||||
|
' [步骤 1] 初始化核心手术刀引擎
|
||||||
|
Set engine = New cConditionEngine
|
||||||
|
|
||||||
|
' [步骤 2] 从工厂获取用户指定的策略
|
||||||
|
Set processor = mProcessorFactory.GetProcessor(strategyType)
|
||||||
|
|
||||||
|
' [步骤 3] 将总库、精准目标集合扔给策略对象执行
|
||||||
|
processor.ProcessSpec allBOMs, targetBOMs, paramName, newValue, engine
|
||||||
|
|
||||||
|
' [步骤 4] 将修改过的数据批量写回 Excel (只写 IsDirty=True 的对象)
|
||||||
|
mBOMRepository.SaveAll ws, allBOMs
|
||||||
|
|
||||||
|
' 释放资源
|
||||||
|
Set processor = Nothing
|
||||||
|
Set engine = Nothing
|
||||||
|
|
||||||
|
End Sub
|
||||||
270
VBA/Modules/mTestEngine.bas
Normal file
270
VBA/Modules/mTestEngine.bas
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
' ==============================================================================
|
||||||
|
' 模块名称: mTestEngine
|
||||||
|
' 模块类别: 标准模块 (Standard Module)
|
||||||
|
' 模块职责: 专门用于测试 cConditionEngine 类的逻辑准确性 (引入轻量级单元测试框架)
|
||||||
|
' ==============================================================================
|
||||||
|
Option Explicit
|
||||||
|
|
||||||
|
' 定义模块级变量,用于统计测试结果
|
||||||
|
Private passCount As Integer
|
||||||
|
Private failCount As Integer
|
||||||
|
|
||||||
|
Public Sub RunBOMForgeTests()
|
||||||
|
' 初始化计数器
|
||||||
|
passCount = 0
|
||||||
|
failCount = 0
|
||||||
|
|
||||||
|
' 执行条件引擎测试
|
||||||
|
Call Test_ConditionEngine
|
||||||
|
|
||||||
|
' 执行数据模型测试
|
||||||
|
Call Test_cBOMRow
|
||||||
|
|
||||||
|
' 执行存储库数据层测试
|
||||||
|
Call Test_BOMRepository
|
||||||
|
|
||||||
|
' 执行业务策略层测试
|
||||||
|
Call Test_Processors
|
||||||
|
|
||||||
|
' 执行调度中枢联调测试 (新增)
|
||||||
|
Call Test_Manager_Pipeline
|
||||||
|
|
||||||
|
' ---- 输出测试汇总 ----
|
||||||
|
Debug.Print "-----------------------------------------------"
|
||||||
|
Debug.Print "测试汇总: 共 " & (passCount + failCount) & " 个用例"
|
||||||
|
Debug.Print "通过: " & passCount & " 失败: " & failCount
|
||||||
|
If failCount = 0 Then
|
||||||
|
Debug.Print ">>> 状态: 全部通过! (ALL PASS)"
|
||||||
|
Else
|
||||||
|
Debug.Print ">>> 状态: 存在失败用例,请检查逻辑!"
|
||||||
|
End If
|
||||||
|
Debug.Print "========== BOMForge 测试框架结束 =========="
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub Test_ConditionEngine()
|
||||||
|
Dim conditionEngine As cConditionEngine
|
||||||
|
Set conditionEngine = New cConditionEngine
|
||||||
|
|
||||||
|
Dim originStr As String
|
||||||
|
Dim expectedStr As String
|
||||||
|
Dim paramName As String
|
||||||
|
Dim newValue As String
|
||||||
|
|
||||||
|
paramName = "lcfw"
|
||||||
|
newValue = "M19"
|
||||||
|
|
||||||
|
Debug.Print "========== 1. 开始测试: cConditionEngine =========="
|
||||||
|
|
||||||
|
' ---- 测试用例 1: 盘止钉 (纯 OR 链,无 AND,无括号) ----
|
||||||
|
originStr = "lcfw=M01 OR lcfw=M02 OR lcfw=M30"
|
||||||
|
expectedStr = "lcfw=M01 OR lcfw=M02 OR lcfw=M30 OR lcfw=M19"
|
||||||
|
AssertEqual "场景1_盘止钉", expectedStr, conditionEngine.InjectOr(originStr, paramName, newValue)
|
||||||
|
|
||||||
|
' ---- 测试用例 2: 接头 (括号包裹的 OR 链,外面有 AND) ----
|
||||||
|
originStr = "(azxs=A0 OR azxs=AT OR azxs=AH) AND gclj=Z14 AND (lcfw=M12 OR lcfw=M13 OR lcfw=M14 OR lcfw=M15 OR lcfw=M16)"
|
||||||
|
expectedStr = "(azxs=A0 OR azxs=AT OR azxs=AH) AND gclj=Z14 AND (lcfw=M12 OR lcfw=M13 OR lcfw=M14 OR lcfw=M15 OR lcfw=M16 OR lcfw=M19)"
|
||||||
|
AssertEqual "场景2_接头", expectedStr, conditionEngine.InjectOr(originStr, paramName, newValue)
|
||||||
|
|
||||||
|
' ---- 测试用例 3: 弹性元件 (孤立的参数,外面有 AND,需自动加括号) ----
|
||||||
|
originStr = "lcfw=M01 AND gclj!=M20"
|
||||||
|
expectedStr = "(lcfw=M01 OR lcfw=M19) AND gclj!=M20"
|
||||||
|
AssertEqual "场景3_弹簧管", expectedStr, conditionEngine.InjectOr(originStr, paramName, newValue)
|
||||||
|
|
||||||
|
' ---- 测试用例 4: 封口片 (右侧紧跟 AND 的情况) ----
|
||||||
|
originStr = "(lcfw=M12 OR lcfw=M13 OR lcfw=M14 OR lcfw=M15 OR lcfw=M16)AND gclj!=M20"
|
||||||
|
expectedStr = "(lcfw=M12 OR lcfw=M13 OR lcfw=M14 OR lcfw=M15 OR lcfw=M16 OR lcfw=M19)AND gclj!=M20"
|
||||||
|
AssertEqual "场景4_封口片", expectedStr, conditionEngine.InjectOr(originStr, paramName, newValue)
|
||||||
|
|
||||||
|
' ---- 测试用例 5: 防御性测试 (本身已经包含了目标条件,看是否会重复添加) ----
|
||||||
|
originStr = "lcfw=M01 OR lcfw=M19"
|
||||||
|
expectedStr = "lcfw=M01 OR lcfw=M19"
|
||||||
|
AssertEqual "场景5_防重复", expectedStr, conditionEngine.InjectOr(originStr, paramName, newValue)
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub Test_cBOMRow()
|
||||||
|
Debug.Print ""
|
||||||
|
Debug.Print "========== 2. 开始测试: cBOMRow 数据模型 =========="
|
||||||
|
|
||||||
|
Dim bomRow As cBOMRow
|
||||||
|
Set bomRow = New cBOMRow
|
||||||
|
|
||||||
|
AssertEqual "初始脏标记测试", "False", CStr(bomRow.IsDirty)
|
||||||
|
|
||||||
|
bomRow.RowNo = 700
|
||||||
|
bomRow.ModuleType = "接头"
|
||||||
|
bomRow.Code = "01081014361"
|
||||||
|
bomRow.ItemName = "径向高压接头"
|
||||||
|
bomRow.Condition = "(azxs=A0) AND gclj=Z14"
|
||||||
|
|
||||||
|
AssertEqual "赋值后脏标记测试", "True", CStr(bomRow.IsDirty)
|
||||||
|
|
||||||
|
bomRow.ResetDirtyFlag
|
||||||
|
AssertEqual "重置后脏标记测试", "False", CStr(bomRow.IsDirty)
|
||||||
|
|
||||||
|
bomRow.Condition = "(azxs=A0) AND gclj=Z14"
|
||||||
|
AssertEqual "重复赋值脏标记测试", "False", CStr(bomRow.IsDirty)
|
||||||
|
|
||||||
|
bomRow.Condition = "(azxs=A0) AND gclj=Z14 AND (lcfw=M19)"
|
||||||
|
AssertEqual "状态变更脏标记测试", "True", CStr(bomRow.IsDirty)
|
||||||
|
AssertEqual "属性读取测试", "(azxs=A0) AND gclj=Z14 AND (lcfw=M19)", bomRow.Condition
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub Test_BOMRepository()
|
||||||
|
Debug.Print ""
|
||||||
|
Debug.Print "========== 3. 开始测试: mBOMRepository 数据访问层 =========="
|
||||||
|
|
||||||
|
Dim wb As Workbook
|
||||||
|
Dim wsTemp As Worksheet
|
||||||
|
Set wb = ThisWorkbook
|
||||||
|
|
||||||
|
Application.DisplayAlerts = False
|
||||||
|
On Error Resume Next
|
||||||
|
wb.Worksheets("BOMForge_Test_Sandbox").Delete
|
||||||
|
On Error GoTo 0
|
||||||
|
Set wsTemp = wb.Worksheets.Add
|
||||||
|
wsTemp.Name = "BOMForge_Test_Sandbox"
|
||||||
|
|
||||||
|
wsTemp.Cells(3, 1).value = "行号"
|
||||||
|
wsTemp.Cells(4, 1).value = 10
|
||||||
|
wsTemp.Cells(4, 2).value = "接头"
|
||||||
|
wsTemp.Cells(4, 6).value = "lcfw=M01"
|
||||||
|
|
||||||
|
wsTemp.Cells(5, 1).value = 20
|
||||||
|
wsTemp.Cells(5, 2).value = "盘止钉"
|
||||||
|
wsTemp.Cells(5, 6).value = "lcfw=M02"
|
||||||
|
|
||||||
|
Dim bomColl As Collection
|
||||||
|
Set bomColl = LoadAllBOMs(wsTemp)
|
||||||
|
|
||||||
|
AssertEqual "加载集合数量", "2", CStr(bomColl.count)
|
||||||
|
AssertEqual "验证读取第一行", "接头", bomColl(1).ModuleType
|
||||||
|
AssertEqual "验证加载后脏标记为空", "False", CStr(bomColl(1).IsDirty)
|
||||||
|
|
||||||
|
bomColl(2).Condition = "lcfw=M02 OR lcfw=M19"
|
||||||
|
SaveAll wsTemp, bomColl
|
||||||
|
|
||||||
|
AssertEqual "验证Excel未被错误修改", "lcfw=M01", wsTemp.Cells(4, 6).value
|
||||||
|
AssertEqual "验证Excel正确更新", "lcfw=M02 OR lcfw=M19", wsTemp.Cells(5, 6).value
|
||||||
|
AssertEqual "验证保存后脏标记重置", "False", CStr(bomColl(2).IsDirty)
|
||||||
|
|
||||||
|
Dim newRow As cBOMRow
|
||||||
|
Set newRow = InsertNewBOMRow(bomColl, bomColl(1))
|
||||||
|
newRow.Condition = "lcfw=M19"
|
||||||
|
|
||||||
|
SaveAll wsTemp, bomColl
|
||||||
|
|
||||||
|
AssertEqual "验证新增行集合追加", "3", CStr(bomColl.count)
|
||||||
|
AssertEqual "验证新增行Excel保存", "lcfw=M19", wsTemp.Cells(6, 6).value
|
||||||
|
AssertEqual "验证新增行Excel映射行号", "6", CStr(newRow.ExcelRowIndex)
|
||||||
|
|
||||||
|
wsTemp.Delete
|
||||||
|
Application.DisplayAlerts = True
|
||||||
|
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub Test_Processors()
|
||||||
|
Debug.Print ""
|
||||||
|
Debug.Print "========== 4. 开始测试: 业务策略层 (Strategy) 细粒度新架构 =========="
|
||||||
|
|
||||||
|
Dim engine As cConditionEngine
|
||||||
|
Set engine = New cConditionEngine
|
||||||
|
|
||||||
|
Dim paramName As String: paramName = "lcfw"
|
||||||
|
Dim newValue As String: newValue = "M19"
|
||||||
|
|
||||||
|
Dim allBOMs As New Collection
|
||||||
|
Dim targetBOMs As New Collection
|
||||||
|
Dim row1 As New cBOMRow, row2 As New cBOMRow, row3 As New cBOMRow
|
||||||
|
|
||||||
|
row1.ItemName = "径向高压接头": row1.Condition = "lcfw=M12": allBOMs.Add row1
|
||||||
|
row2.ItemName = "径向高压接头": row2.Condition = "lcfw=M13": allBOMs.Add row2
|
||||||
|
row3.ItemName = "弹簧管": row3.Condition = "lcfw=M01 AND gclj!=M20": allBOMs.Add row3
|
||||||
|
|
||||||
|
' 【核心逻辑】模拟 UI 层仅将用户勾选的具体行 (比如 row1) 加入 targetBOMs 集合
|
||||||
|
targetBOMs.Add row1
|
||||||
|
|
||||||
|
' 测试 1: 追加策略 (AppendOnly)
|
||||||
|
Dim strategyAppend As IModuleProcessor
|
||||||
|
Set strategyAppend = New cProcessor_AppendOnly
|
||||||
|
|
||||||
|
strategyAppend.ProcessSpec allBOMs, targetBOMs, paramName, newValue, engine
|
||||||
|
|
||||||
|
AssertEqual "追加策略_选中的变种1被更新", "lcfw=M12 OR lcfw=M19", row1.Condition
|
||||||
|
AssertEqual "追加策略_未选中的变种2保持不变", "lcfw=M13", row2.Condition
|
||||||
|
AssertEqual "追加策略_不影响其他物料", "lcfw=M01 AND gclj!=M20", row3.Condition
|
||||||
|
|
||||||
|
' 测试 2: 克隆新增策略 (CloneNew)
|
||||||
|
Dim strategyClone As IModuleProcessor
|
||||||
|
Set strategyClone = New cProcessor_CloneNew
|
||||||
|
|
||||||
|
Dim initialCount As Integer: initialCount = allBOMs.count
|
||||||
|
|
||||||
|
' 模拟 UI 选择弹簧管作为克隆母版
|
||||||
|
Set targetBOMs = New Collection
|
||||||
|
targetBOMs.Add row3
|
||||||
|
|
||||||
|
strategyClone.ProcessSpec allBOMs, targetBOMs, paramName, newValue, engine
|
||||||
|
|
||||||
|
AssertEqual "克隆策略_总库数量增加", CStr(initialCount + 1), CStr(allBOMs.count)
|
||||||
|
AssertEqual "克隆策略_新行条件纯粹准确", "lcfw=M19", allBOMs(allBOMs.count).Condition
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 新增:端到端流水线集成测试
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub Test_Manager_Pipeline()
|
||||||
|
Debug.Print ""
|
||||||
|
Debug.Print "========== 5. 开始测试: 调度中枢集成测试 (Pipeline) =========="
|
||||||
|
|
||||||
|
Dim wb As Workbook
|
||||||
|
Dim wsTemp As Worksheet
|
||||||
|
Set wb = ThisWorkbook
|
||||||
|
|
||||||
|
Application.DisplayAlerts = False
|
||||||
|
On Error Resume Next
|
||||||
|
wb.Worksheets("BOMForge_Test_Pipeline").Delete
|
||||||
|
On Error GoTo 0
|
||||||
|
Set wsTemp = wb.Worksheets.Add
|
||||||
|
wsTemp.Name = "BOMForge_Test_Pipeline"
|
||||||
|
|
||||||
|
wsTemp.Cells(3, 1).value = "行号"
|
||||||
|
wsTemp.Cells(4, 1).value = 700: wsTemp.Cells(4, 4).value = "径向高压接头": wsTemp.Cells(4, 6).value = "lcfw=M12"
|
||||||
|
wsTemp.Cells(5, 1).value = 710: wsTemp.Cells(5, 4).value = "径向高压接头": wsTemp.Cells(5, 6).value = "lcfw=M13"
|
||||||
|
wsTemp.Cells(6, 1).value = 800: wsTemp.Cells(6, 4).value = "弹簧管": wsTemp.Cells(6, 6).value = "lcfw=M01"
|
||||||
|
|
||||||
|
' 模拟 UI 打开时加载数据
|
||||||
|
Dim globalBOMs As Collection
|
||||||
|
Set globalBOMs = mSpecAdditionManager.LoadData(wsTemp)
|
||||||
|
|
||||||
|
' 模拟 UI 细粒度勾选:只选中了第5行 (即集合中的第2个对象)
|
||||||
|
Dim uiSelectedBOMs As New Collection
|
||||||
|
uiSelectedBOMs.Add globalBOMs(2)
|
||||||
|
|
||||||
|
' 执行调度中枢的追加流水线
|
||||||
|
mSpecAdditionManager.ExecutePipeline wsTemp, globalBOMs, uiSelectedBOMs, "lcfw", "M19", "APPEND"
|
||||||
|
|
||||||
|
AssertEqual "流水线_追加_目标行1未选中(不变)", "lcfw=M12", wsTemp.Cells(4, 6).value
|
||||||
|
AssertEqual "流水线_追加_目标行2被选中更新", "lcfw=M13 OR lcfw=M19", wsTemp.Cells(5, 6).value
|
||||||
|
AssertEqual "流水线_追加_无关行不变", "lcfw=M01", wsTemp.Cells(6, 6).value
|
||||||
|
|
||||||
|
wsTemp.Delete
|
||||||
|
Application.DisplayAlerts = True
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ==============================================================================
|
||||||
|
' 内部辅助方法:断言测试结果
|
||||||
|
' ==============================================================================
|
||||||
|
Private Sub AssertEqual(testName As String, expected As String, actual As String)
|
||||||
|
If expected = actual Then
|
||||||
|
Debug.Print "[PASS] " & testName
|
||||||
|
passCount = passCount + 1
|
||||||
|
Else
|
||||||
|
Debug.Print "[FAIL] " & testName
|
||||||
|
Debug.Print " 期望结果: " & expected
|
||||||
|
Debug.Print " 实际结果: " & actual
|
||||||
|
failCount = failCount + 1
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
Reference in New Issue
Block a user