feat: integrate inventory verification for component materials in BOM extraction
All checks were successful
NTFY Notification / notify (push) Successful in 7s
All checks were successful
NTFY Notification / notify (push) Successful in 7s
Implement inventory-based component selection logic that accumulates demand
across orders and falls back to sub-components (joint + element) when stock
is insufficient.
**Changes:**
- M04_Config: Add inventory worksheet configuration constants
- INVENTORY_SHEET_NAME = "现存量"
- INVENTORY_HEADER_ROW = 3 (headers on row 3)
- INVENTORY_COL_CODE = "B" (material code)
- INVENTORY_COL_QTY = "J" (stock quantity)
- M08_ComponentProcessor: Implement inventory tracking and verification
- Add module-level variables: g_InventoryDict, g_AccumulatedDemandDict
- Add LoadInventoryData() to load stock from [现存量] worksheet
- Add InitComponentProcessorWithInventory() for initialization with inventory
- Rewrite CheckComponentInventory() with actual inventory logic:
* Calculate cumulative demand = orderQty × bomQty + previousAccumulated
* Compare with available stock
* Return True if stock sufficient, False otherwise
* Update accumulated demand after each order
- Update ProcessComponentRecord() to accept orderQty parameter
- M09_BOMExtractor: Integrate inventory check into main workflow
- Modify ReadInputModels() to read 3 columns (orderNo, model, qty)
- Initialize component processor with inventory support
- Extract order quantity from column C and pass through call chain
- Update ProcessSingleModel() and MatchAllMaterialTypesWithValidation()
signatures to accept orderQty parameter
**Logic Example:**
- 3 orders (PO-001, PO-003, PO-006) use component A
- Each order: quantity=2, BOM qty=1, stock=5
- PO-001: cumulative=2, stock 5>=2 ✓ → return component
- PO-003: cumulative=4, stock 5>=4 ✓ → return component
- PO-006: cumulative=6, stock 5<6 ✗ → return sub-components
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,14 @@ Public Const BOMLIB_COL_ELEMENT_NAME As String = "弹性元件名称"
|
|||||||
Public Const BOMLIB_COL_ELEMENT_CODE As String = "弹性元件编码"
|
Public Const BOMLIB_COL_ELEMENT_CODE As String = "弹性元件编码"
|
||||||
Public Const BOMLIB_COL_ELEMENT_QTY As String = "弹性元件数量"
|
Public Const BOMLIB_COL_ELEMENT_QTY As String = "弹性元件数量"
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 现存量工作表配置常量
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Const INVENTORY_SHEET_NAME As String = "现存量"
|
||||||
|
Public Const INVENTORY_HEADER_ROW As Long = 3 ' 表头在第3行
|
||||||
|
Public Const INVENTORY_COL_CODE As String = "B" ' B列 = 物料编码
|
||||||
|
Public Const INVENTORY_COL_QTY As String = "J" ' J列 = 库存数量
|
||||||
|
|
||||||
' BOM库条件字段列表(所有可能的条件字段)
|
' BOM库条件字段列表(所有可能的条件字段)
|
||||||
Public Function GetBOMConditionFields() As Variant
|
Public Function GetBOMConditionFields() As Variant
|
||||||
GetBOMConditionFields = Array( _
|
GetBOMConditionFields = Array( _
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ Option Explicit
|
|||||||
' 模块级变量 - 错误记录器
|
' 模块级变量 - 错误记录器
|
||||||
Private g_Logger As clsErrorLogger
|
Private g_Logger As clsErrorLogger
|
||||||
|
|
||||||
|
' 模块级变量 - 库存追踪
|
||||||
|
Private g_InventoryDict As Object ' 部件编码 -> 现存量
|
||||||
|
Private g_AccumulatedDemandDict As Object ' 部件编码 -> 累计需求量
|
||||||
|
|
||||||
' ------------------------------------------------------------------------------
|
' ------------------------------------------------------------------------------
|
||||||
' 初始化部件处理器
|
' 初始化部件处理器
|
||||||
' ------------------------------------------------------------------------------
|
' ------------------------------------------------------------------------------
|
||||||
@@ -30,6 +34,86 @@ Public Sub InitComponentProcessor(logger As clsErrorLogger)
|
|||||||
Set g_Logger = logger
|
Set g_Logger = logger
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 初始化部件处理器(带库存校验)
|
||||||
|
'
|
||||||
|
' 输入:
|
||||||
|
' logger - 错误记录器
|
||||||
|
' inventoryWb - 包含现存量工作表的工作簿(通常是ThisWorkbook)
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Public Sub InitComponentProcessorWithInventory( _
|
||||||
|
logger As clsErrorLogger, _
|
||||||
|
inventoryWb As Workbook _
|
||||||
|
)
|
||||||
|
Set g_Logger = logger
|
||||||
|
Call LoadInventoryData(inventoryWb)
|
||||||
|
Set g_AccumulatedDemandDict = CreateObject("Scripting.Dictionary")
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
' 加载库存数据
|
||||||
|
'
|
||||||
|
' 输入:
|
||||||
|
' inventoryWb - 包含现存量工作表的工作簿
|
||||||
|
'
|
||||||
|
' 逻辑:
|
||||||
|
' 1. 查找[现存量]工作表
|
||||||
|
' 2. 如果未找到,记录警告并使用空字典(所有库存视为0)
|
||||||
|
' 3. 从第4行开始读取数据(表头在第3行)
|
||||||
|
' 4. B列 = 物料编码,J列 = 库存数量
|
||||||
|
' 5. 存储到 g_InventoryDict 中
|
||||||
|
' ------------------------------------------------------------------------------
|
||||||
|
Private Sub LoadInventoryData(ByVal inventoryWb As Workbook)
|
||||||
|
On Error GoTo ErrorHandler
|
||||||
|
|
||||||
|
Set g_InventoryDict = CreateObject("Scripting.Dictionary")
|
||||||
|
|
||||||
|
' 检查现存量工作表是否存在
|
||||||
|
Dim wsInventory As Worksheet
|
||||||
|
On Error Resume Next
|
||||||
|
Set wsInventory = inventoryWb.Sheets(INVENTORY_SHEET_NAME)
|
||||||
|
On Error GoTo ErrorHandler
|
||||||
|
|
||||||
|
If wsInventory Is Nothing Then
|
||||||
|
' 未找到现存量工作表,记录警告并使用空字典(所有库存视为0)
|
||||||
|
If Not g_Logger Is Nothing Then
|
||||||
|
g_Logger.RecordWarning 0, "M08.LoadInventoryData", "InventorySheetMissing", _
|
||||||
|
"未找到[" & INVENTORY_SHEET_NAME & "]工作表,所有部件库存将视为0", ""
|
||||||
|
End If
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 查找最后一行
|
||||||
|
Dim lastRow As Long
|
||||||
|
lastRow = wsInventory.Cells(wsInventory.Rows.Count, 2).End(xlUp).Row ' B列
|
||||||
|
|
||||||
|
If lastRow < INVENTORY_HEADER_ROW + 1 Then
|
||||||
|
Exit Sub ' 没有数据
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 从第4行开始读取数据(表头在第3行)
|
||||||
|
Dim i As Long
|
||||||
|
For i = INVENTORY_HEADER_ROW + 1 To lastRow
|
||||||
|
Dim materialCode As String
|
||||||
|
Dim stockQty As Variant
|
||||||
|
|
||||||
|
materialCode = Trim(CStr(wsInventory.Cells(i, INVENTORY_COL_CODE).Value))
|
||||||
|
stockQty = wsInventory.Cells(i, INVENTORY_COL_QTY).Value
|
||||||
|
|
||||||
|
If Len(materialCode) > 0 And IsNumeric(stockQty) Then
|
||||||
|
g_InventoryDict(materialCode) = CLng(stockQty)
|
||||||
|
End If
|
||||||
|
Next i
|
||||||
|
|
||||||
|
Exit Sub
|
||||||
|
|
||||||
|
ErrorHandler:
|
||||||
|
If Not g_Logger Is Nothing Then
|
||||||
|
g_Logger.RecordWarning 0, "M08.LoadInventoryData", "LoadError", _
|
||||||
|
"加载库存数据失败: " & Err.Description, ""
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
|
||||||
' ------------------------------------------------------------------------------
|
' ------------------------------------------------------------------------------
|
||||||
' 主入口: 处理"部件"记录,返回物料集合
|
' 主入口: 处理"部件"记录,返回物料集合
|
||||||
'
|
'
|
||||||
@@ -58,7 +142,8 @@ Public Function ProcessComponentRecord( _
|
|||||||
ByVal wsComponent As Worksheet, _
|
ByVal wsComponent As Worksheet, _
|
||||||
ByVal params As Object, _
|
ByVal params As Object, _
|
||||||
ByVal logger As clsErrorLogger, _
|
ByVal logger As clsErrorLogger, _
|
||||||
ByVal matchedRowNum As Long _
|
ByVal matchedRowNum As Long, _
|
||||||
|
ByVal orderQty As Long _
|
||||||
) As Collection
|
) As Collection
|
||||||
On Error GoTo ErrorHandler
|
On Error GoTo ErrorHandler
|
||||||
|
|
||||||
@@ -88,7 +173,7 @@ Public Function ProcessComponentRecord( _
|
|||||||
Set headerMap = M07_BOMMatcher.BuildWorksheetHeaderMap(wsComponent)
|
Set headerMap = M07_BOMMatcher.BuildWorksheetHeaderMap(wsComponent)
|
||||||
|
|
||||||
' 步骤4: 检查部件库存
|
' 步骤4: 检查部件库存
|
||||||
If CheckComponentInventory(wsComponent, matchedRowNum, headerMap) Then
|
If CheckComponentInventory(wsComponent, matchedRowNum, headerMap, orderQty) Then
|
||||||
' 库存充足,返回部件物料
|
' 库存充足,返回部件物料
|
||||||
Dim componentInfo As Object
|
Dim componentInfo As Object
|
||||||
Set componentInfo = ExtractComponentInfo(wsComponent, matchedRowNum, headerMap, "部件")
|
Set componentInfo = ExtractComponentInfo(wsComponent, matchedRowNum, headerMap, "部件")
|
||||||
@@ -131,39 +216,126 @@ ErrorHandler:
|
|||||||
End Function
|
End Function
|
||||||
|
|
||||||
' ------------------------------------------------------------------------------
|
' ------------------------------------------------------------------------------
|
||||||
' 检查部件库存状态(预留接口)
|
' 检查部件库存状态(集成库存校验)
|
||||||
'
|
'
|
||||||
' 输入:
|
' 输入:
|
||||||
' wsComponent - "部件"工作表
|
' wsComponent - "部件"工作表
|
||||||
' rowNum - 匹配到的行号
|
' rowNum - 匹配到的行号
|
||||||
' headerMap - 表头映射
|
' headerMap - 表头映射
|
||||||
|
' orderQty - 订单数量
|
||||||
'
|
'
|
||||||
' 输出:
|
' 输出:
|
||||||
' Boolean - True表示有库存,False表示无库存
|
' Boolean - True表示库存充足,False表示库存不足
|
||||||
'
|
'
|
||||||
' 注意:
|
' 逻辑:
|
||||||
' - 当前版本默认返回True(库存充足)
|
' 1. 提取部件编码
|
||||||
' - 预留接口,未来可连接ERP/库存系统
|
' 2. 获取BOM需求量(部件工作表中的数量列)
|
||||||
' - 可扩展为查询库存Excel表或API
|
' 3. 检查库存数据是否存在
|
||||||
|
' 4. 计算累计需求量 = 订单数量 × BOM需求量 + 之前累计需求
|
||||||
|
' 5. 比较库存和需求,更新累计需求量
|
||||||
|
'
|
||||||
|
' 示例:
|
||||||
|
' 订单1、订单3、订单6都用部件A,每个订单数量=2,BOM需求量=1,现存量=5
|
||||||
|
' - 订单1:累计需求 = 2×1 + 0 = 2,5 >= 2 ✓ true(使用部件)
|
||||||
|
' - 订单3:累计需求 = 2×1 + 2 = 4,5 >= 4 ✓ true(使用部件)
|
||||||
|
' - 订单6:累计需求 = 2×1 + 4 = 6,5 < 6 ✗ false(使用子件)
|
||||||
' ------------------------------------------------------------------------------
|
' ------------------------------------------------------------------------------
|
||||||
Private Function CheckComponentInventory( _
|
Private Function CheckComponentInventory( _
|
||||||
ByVal wsComponent As Worksheet, _
|
ByVal wsComponent As Worksheet, _
|
||||||
ByVal rowNum As Long, _
|
ByVal rowNum As Long, _
|
||||||
ByVal headerMap As Object _
|
ByVal headerMap As Object, _
|
||||||
|
ByVal orderQty As Long _
|
||||||
) As Boolean
|
) As Boolean
|
||||||
' TODO: 连接库存系统查询实际库存
|
On Error GoTo ErrorHandler
|
||||||
' 当前版本默认返回True(库存充足)
|
|
||||||
|
|
||||||
' 示例扩展代码(注释):
|
' 步骤1: 提取部件编码
|
||||||
' If headerMap.Exists("库存数量") Then
|
Dim codeCol As Long
|
||||||
' Dim stockQty As Long
|
Dim codeKey As String
|
||||||
' stockQty = CLng(wsComponent.Cells(rowNum, headerMap("库存数量")).Value)
|
codeKey = LCase(BOMLIB_COL_CODE)
|
||||||
' CheckComponentInventory = (stockQty > 0)
|
|
||||||
' Else
|
|
||||||
' CheckComponentInventory = True
|
|
||||||
' End If
|
|
||||||
|
|
||||||
CheckComponentInventory = True
|
If Not headerMap.Exists(codeKey) Then
|
||||||
|
' 没有编码列,默认返回True(库存充足)
|
||||||
|
CheckComponentInventory = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
codeCol = headerMap(codeKey)
|
||||||
|
Dim componentCode As String
|
||||||
|
componentCode = Trim(CStr(wsComponent.Cells(rowNum, codeCol).Value))
|
||||||
|
|
||||||
|
If Len(componentCode) = 0 Then
|
||||||
|
' 没有编码,默认返回True
|
||||||
|
CheckComponentInventory = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 步骤2: 获取BOM需求量(部件工作表中的数量列)
|
||||||
|
Dim bomQty As Long
|
||||||
|
bomQty = 1 ' 默认为1
|
||||||
|
|
||||||
|
Dim qtyKey As String
|
||||||
|
qtyKey = LCase(BOMLIB_COL_QTY)
|
||||||
|
|
||||||
|
If headerMap.Exists(qtyKey) Then
|
||||||
|
Dim qtyCol As Long
|
||||||
|
Dim qtyValue As Variant
|
||||||
|
qtyCol = headerMap(qtyKey)
|
||||||
|
qtyValue = wsComponent.Cells(rowNum, qtyCol).Value
|
||||||
|
|
||||||
|
If IsNumeric(qtyValue) Then
|
||||||
|
bomQty = CLng(qtyValue)
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 步骤3: 检查库存数据是否存在
|
||||||
|
If g_InventoryDict Is Nothing Or Not g_InventoryDict.Exists(componentCode) Then
|
||||||
|
' 未找到库存数据,记录警告并返回False(库存不足)
|
||||||
|
If Not g_Logger Is Nothing Then
|
||||||
|
g_Logger.RecordWarning rowNum, "M08.CheckComponentInventory", "InventoryNotFound", _
|
||||||
|
"部件[" & componentCode & "]未找到库存数据,视为库存不足", ""
|
||||||
|
End If
|
||||||
|
CheckComponentInventory = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' 步骤4: 计算累计需求量
|
||||||
|
Dim currentDemand As Long
|
||||||
|
currentDemand = orderQty * bomQty
|
||||||
|
|
||||||
|
Dim accumulatedDemand As Long
|
||||||
|
If g_AccumulatedDemandDict.Exists(componentCode) Then
|
||||||
|
accumulatedDemand = g_AccumulatedDemandDict(componentCode)
|
||||||
|
End If
|
||||||
|
|
||||||
|
Dim totalDemand As Long
|
||||||
|
totalDemand = accumulatedDemand + currentDemand
|
||||||
|
|
||||||
|
' 步骤5: 比较库存和需求
|
||||||
|
Dim stockQty As Long
|
||||||
|
stockQty = g_InventoryDict(componentCode)
|
||||||
|
|
||||||
|
If stockQty >= totalDemand Then
|
||||||
|
' 库存充足,更新累计需求量
|
||||||
|
g_AccumulatedDemandDict(componentCode) = totalDemand
|
||||||
|
CheckComponentInventory = True
|
||||||
|
Else
|
||||||
|
' 库存不足,记录错误
|
||||||
|
If Not g_Logger Is Nothing Then
|
||||||
|
g_Logger.Record rowNum, "M08.CheckComponentInventory", "InsufficientInventory", _
|
||||||
|
"部件[" & componentCode & "]库存不足。库存=" & stockQty & ", 累计需求=" & totalDemand, ""
|
||||||
|
End If
|
||||||
|
CheckComponentInventory = False
|
||||||
|
End If
|
||||||
|
|
||||||
|
Exit Function
|
||||||
|
|
||||||
|
ErrorHandler:
|
||||||
|
If Not g_Logger Is Nothing Then
|
||||||
|
g_Logger.Record rowNum, "M08.CheckComponentInventory", "SystemError", _
|
||||||
|
"库存检查失败: " & Err.Description, ""
|
||||||
|
End If
|
||||||
|
' 出错时返回False(库存不足)
|
||||||
|
CheckComponentInventory = False
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
' ------------------------------------------------------------------------------
|
' ------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -93,7 +93,11 @@ Public Function RunBOMExtraction() As String
|
|||||||
' 初始化各模块
|
' 初始化各模块
|
||||||
M06_ModelParser.InitModelParser g_Logger
|
M06_ModelParser.InitModelParser g_Logger
|
||||||
M07_BOMMatcher.InitBOMMatcher g_Logger
|
M07_BOMMatcher.InitBOMMatcher g_Logger
|
||||||
M08_ComponentProcessor.InitComponentProcessor g_Logger
|
|
||||||
|
' 初始化部件处理器(带库存校验)- 现存量在主工作簿中
|
||||||
|
Dim invWorkbook As Workbook
|
||||||
|
Set invWorkbook = ThisWorkbook ' 现存量在主工作簿中
|
||||||
|
M08_ComponentProcessor.InitComponentProcessorWithInventory g_Logger, invWorkbook
|
||||||
|
|
||||||
' 初始化映射器(新增)
|
' 初始化映射器(新增)
|
||||||
If WorksheetExists(MAPPING_SHEET_NAME) Then
|
If WorksheetExists(MAPPING_SHEET_NAME) Then
|
||||||
@@ -129,12 +133,14 @@ Public Function RunBOMExtraction() As String
|
|||||||
' 处理单个型号
|
' 处理单个型号
|
||||||
Dim productionOrderNo As String
|
Dim productionOrderNo As String
|
||||||
Dim modelString As String
|
Dim modelString As String
|
||||||
|
Dim orderQty As Long
|
||||||
|
|
||||||
productionOrderNo = CStr(inputModels(i, 1)) ' Column A: 生产订单号
|
productionOrderNo = CStr(inputModels(i, 1)) ' Column A: 生产订单号
|
||||||
modelString = CStr(inputModels(i, 2)) ' Column B: 产品型号
|
modelString = CStr(inputModels(i, 2)) ' Column B: 产品型号
|
||||||
|
orderQty = CLng(inputModels(i, 3)) ' Column C: 数量
|
||||||
|
|
||||||
Dim modelResults As Collection
|
Dim modelResults As Collection
|
||||||
Set modelResults = ProcessSingleModel(modelString, g_BOMWorkbook, g_Logger, productionOrderNo)
|
Set modelResults = ProcessSingleModel(modelString, g_BOMWorkbook, g_Logger, productionOrderNo, orderQty)
|
||||||
|
|
||||||
' 合并结果
|
' 合并结果
|
||||||
Dim result As Variant
|
Dim result As Variant
|
||||||
@@ -295,9 +301,9 @@ Private Function ReadInputModels(ByVal ws As Worksheet) As Variant
|
|||||||
Exit Function
|
Exit Function
|
||||||
End If
|
End If
|
||||||
|
|
||||||
' 读取数据到数组(返回2列:生产订单号、产品型号)
|
' 读取数据到数组(返回3列:生产订单号、产品型号、数量)
|
||||||
' 假设生产订单号在A列(列1),产品型号在colIdx列
|
' 假设:A列(1)=生产订单号, B列(2)=产品型号, C列(3)=数量
|
||||||
ReadInputModels = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, colIdx)).Value
|
ReadInputModels = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 3)).Value
|
||||||
Exit Function
|
Exit Function
|
||||||
|
|
||||||
ErrorHandler:
|
ErrorHandler:
|
||||||
@@ -326,7 +332,8 @@ Private Function ProcessSingleModel( _
|
|||||||
ByVal modelString As String, _
|
ByVal modelString As String, _
|
||||||
ByVal bomWb As Workbook, _
|
ByVal bomWb As Workbook, _
|
||||||
ByVal logger As clsErrorLogger, _
|
ByVal logger As clsErrorLogger, _
|
||||||
ByVal productionOrderNo As String _
|
ByVal productionOrderNo As String, _
|
||||||
|
ByVal orderQty As Long _
|
||||||
) As Collection
|
) As Collection
|
||||||
On Error GoTo ErrorHandler
|
On Error GoTo ErrorHandler
|
||||||
|
|
||||||
@@ -348,7 +355,7 @@ Private Function ProcessSingleModel( _
|
|||||||
' 步骤2: 匹配所有物料类型(两阶段)
|
' 步骤2: 匹配所有物料类型(两阶段)
|
||||||
Dim allMaterials As Collection
|
Dim allMaterials As Collection
|
||||||
Dim validation As Object
|
Dim validation As Object
|
||||||
Set allMaterials = MatchAllMaterialTypesWithValidation(params, bomWb, logger, validation)
|
Set allMaterials = MatchAllMaterialTypesWithValidation(params, bomWb, logger, orderQty, validation)
|
||||||
|
|
||||||
' 步骤3: 生成输出行
|
' 步骤3: 生成输出行
|
||||||
Dim remarks As String
|
Dim remarks As String
|
||||||
@@ -414,6 +421,7 @@ Private Function MatchAllMaterialTypesWithValidation( _
|
|||||||
ByVal params As Object, _
|
ByVal params As Object, _
|
||||||
ByVal bomWb As Workbook, _
|
ByVal bomWb As Workbook, _
|
||||||
ByVal logger As clsErrorLogger, _
|
ByVal logger As clsErrorLogger, _
|
||||||
|
ByVal orderQty As Long, _
|
||||||
ByRef outValidation As Object _
|
ByRef outValidation As Object _
|
||||||
) As Collection
|
) As Collection
|
||||||
On Error GoTo ErrorHandler
|
On Error GoTo ErrorHandler
|
||||||
@@ -457,7 +465,7 @@ Private Function MatchAllMaterialTypesWithValidation( _
|
|||||||
|
|
||||||
If bomMatchResult("success") Then
|
If bomMatchResult("success") Then
|
||||||
Set componentMaterials = M08_ComponentProcessor.ProcessComponentRecord( _
|
Set componentMaterials = M08_ComponentProcessor.ProcessComponentRecord( _
|
||||||
ws, params, logger, bomMatchResult("rowNums")(1))
|
ws, params, logger, bomMatchResult("rowNums")(1), orderQty)
|
||||||
|
|
||||||
Debug.Print " -> 返回物料数: " & componentMaterials.count
|
Debug.Print " -> 返回物料数: " & componentMaterials.count
|
||||||
End If
|
End If
|
||||||
|
|||||||
236
docs/Inventory_Check_Implementation_Summary.md
Normal file
236
docs/Inventory_Check_Implementation_Summary.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# 库存校验功能集成总结
|
||||||
|
|
||||||
|
## 实施日期
|
||||||
|
2026-02-24
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
在 `RunBOMExtraction` 过程中集成了库存校验功能,针对部件物料进行库存检查。根据订单顺序累加需求量,当库存充足时使用部件,不足时使用子件(接头+弹性元件)。
|
||||||
|
|
||||||
|
## 修改的文件
|
||||||
|
|
||||||
|
### 1. M04_Config.bas
|
||||||
|
**修改内容:** 添加现存量工作表配置常量
|
||||||
|
|
||||||
|
```vba
|
||||||
|
' 现存量工作表配置常量
|
||||||
|
Public Const INVENTORY_SHEET_NAME As String = "现存量"
|
||||||
|
Public Const INVENTORY_HEADER_ROW As Long = 3 ' 表头在第3行
|
||||||
|
Public Const INVENTORY_COL_CODE As String = "B" ' B列 = 物料编码
|
||||||
|
Public Const INVENTORY_COL_QTY As String = "J" ' J列 = 库存数量
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. M08_ComponentProcessor.bas
|
||||||
|
**修改内容:**
|
||||||
|
|
||||||
|
#### 2.1 添加库存追踪模块级变量
|
||||||
|
```vba
|
||||||
|
' 模块级变量 - 库存追踪
|
||||||
|
Private g_InventoryDict As Object ' 部件编码 -> 现存量
|
||||||
|
Private g_AccumulatedDemandDict As Object ' 部件编码 -> 累计需求量
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 新增 `LoadInventoryData` 函数
|
||||||
|
- 从[现存量]工作表加载库存数据
|
||||||
|
- 表头在第3行,从第4行开始读取
|
||||||
|
- B列 = 物料编码,J列 = 库存数量
|
||||||
|
- 如果未找到工作表,记录警告并使用空字典(所有库存视为0)
|
||||||
|
|
||||||
|
#### 2.3 新增 `InitComponentProcessorWithInventory` 函数
|
||||||
|
- 初始化部件处理器,同时加载库存数据
|
||||||
|
- 创建累计需求量字典
|
||||||
|
- 调用 `LoadInventoryData` 加载库存
|
||||||
|
|
||||||
|
#### 2.4 重写 `CheckComponentInventory` 函数
|
||||||
|
**输入参数:**
|
||||||
|
- `wsComponent` - "部件"工作表
|
||||||
|
- `rowNum` - 匹配到的行号
|
||||||
|
- `headerMap` - 表头映射
|
||||||
|
- `orderQty` - 订单数量(新增)
|
||||||
|
|
||||||
|
**逻辑流程:**
|
||||||
|
1. 提取部件编码
|
||||||
|
2. 获取BOM需求量(部件工作表中的数量列)
|
||||||
|
3. 检查库存数据是否存在
|
||||||
|
4. 计算累计需求量 = 订单数量 × BOM需求量 + 之前累计需求
|
||||||
|
5. 比较库存和需求:
|
||||||
|
- 库存 >= 累计需求 → 返回 True,更新累计需求量
|
||||||
|
- 库存 < 累计需求 → 返回 False,记录错误
|
||||||
|
|
||||||
|
#### 2.5 修改 `ProcessComponentRecord` 函数签名
|
||||||
|
**新增参数:** `orderQty As Long`
|
||||||
|
|
||||||
|
**修改调用:** 传递 `orderQty` 给 `CheckComponentInventory`
|
||||||
|
|
||||||
|
### 3. M09_BOMExtractor.bas
|
||||||
|
**修改内容:**
|
||||||
|
|
||||||
|
#### 3.1 修改 `ReadInputModels` 函数
|
||||||
|
**当前实现:** 读取2列(生产订单号、产品型号)
|
||||||
|
|
||||||
|
**注意:** 计划中提到读取3列(包括数量),但当前代码仍使用 `ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, colIdx)).Value`,需要确保 `colIdx` 变量包含第3列(数量列)。
|
||||||
|
|
||||||
|
**建议修改:**
|
||||||
|
```vba
|
||||||
|
' 确保读取到第3列(数量)
|
||||||
|
ReadInputModels = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 3)).Value
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 修改 `RunBOMExtraction` 主循环
|
||||||
|
**修改点1:** 提取数量列
|
||||||
|
```vba
|
||||||
|
Dim orderQty As Long
|
||||||
|
orderQty = CLng(inputModels(i, 3)) ' Column C: 数量
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改点2:** 初始化部件处理器时传递库存工作簿
|
||||||
|
```vba
|
||||||
|
Dim invWorkbook As Workbook
|
||||||
|
Set invWorkbook = ThisWorkbook ' 现存量在主工作簿中
|
||||||
|
M08_ComponentProcessor.InitComponentProcessorWithInventory g_Logger, invWorkbook
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改点3:** 传递数量给 `ProcessSingleModel`
|
||||||
|
```vba
|
||||||
|
Set modelResults = ProcessSingleModel(modelString, g_BOMWorkbook, g_Logger, productionOrderNo, orderQty)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3 修改 `ProcessSingleModel` 函数签名
|
||||||
|
**新增参数:** `orderQty As Long`
|
||||||
|
|
||||||
|
**修改调用:** 传递 `orderQty` 给 `MatchAllMaterialTypesWithValidation`
|
||||||
|
|
||||||
|
#### 3.4 修改 `MatchAllMaterialTypesWithValidation` 函数签名
|
||||||
|
**新增参数:** `orderQty As Long`
|
||||||
|
|
||||||
|
**修改调用:** 传递 `orderQty` 给 `ProcessComponentRecord`
|
||||||
|
|
||||||
|
## 数据流程
|
||||||
|
|
||||||
|
### 输入数据
|
||||||
|
**[产品型号]工作表:**
|
||||||
|
| 生产订单号 | 产品型号 | 数量 |
|
||||||
|
|-----------|---------|------|
|
||||||
|
| PO-001 | YTHN-...| 2 |
|
||||||
|
| PO-003 | YTHN-...| 2 |
|
||||||
|
| PO-006 | YTHN-...| 2 |
|
||||||
|
|
||||||
|
**[现存量]工作表:**
|
||||||
|
(第3行表头)
|
||||||
|
| ... | 物料编码 | ... | 库存数量 |
|
||||||
|
| ... | COMP001 | ... | 5 |
|
||||||
|
|
||||||
|
### 处理逻辑
|
||||||
|
1. **初始化阶段:**
|
||||||
|
- `InitComponentProcessorWithInventory` 加载库存数据到 `g_InventoryDict`
|
||||||
|
- 创建空的 `g_AccumulatedDemandDict` 用于追踪累计需求
|
||||||
|
|
||||||
|
2. **订单处理阶段(按顺序):**
|
||||||
|
- PO-001: 累计需求 = 2×1 + 0 = 2,库存5 >= 2 ✓ → 返回部件
|
||||||
|
- PO-003: 累计需求 = 2×1 + 2 = 4,库存5 >= 4 ✓ → 返回部件
|
||||||
|
- PO-006: 累计需求 = 2×1 + 4 = 6,库存5 < 6 ✗ → 返回子件(接头+弹性元件)
|
||||||
|
|
||||||
|
3. **错误记录:**
|
||||||
|
- 库存不足时记录错误(Blocking Error)
|
||||||
|
- 未找到库存数据时记录警告(Non-blocking Warning)
|
||||||
|
|
||||||
|
### 输出结果
|
||||||
|
**BOM提取结果:**
|
||||||
|
| 生产订单号 | 原始产品型号 | 物料类型 | 物料名称 | 物料编码 | 物料数量 | 提取备注 |
|
||||||
|
|-----------|-------------|---------|---------|---------|---------|---------|
|
||||||
|
| PO-001 | YTHN-... | 部件 | 部件A | COMP001 | 1 | |
|
||||||
|
| PO-003 | YTHN-... | 部件 | 部件A | COMP001 | 1 | |
|
||||||
|
| PO-006 | YTHN-... | 接头 | 接头B | JOINT01 | 1 | 部件无库存,使用子件 |
|
||||||
|
| PO-006 | YTHN-... | 弹性元件| 元件C | ELEM01 | 1 | 部件无库存,使用子件 |
|
||||||
|
|
||||||
|
## 测试要点
|
||||||
|
|
||||||
|
### 功能测试
|
||||||
|
1. **正常库存场景:**
|
||||||
|
- 准备测试数据:3个订单,库存=5
|
||||||
|
- 验证前2个订单返回部件
|
||||||
|
- 验证第3个订单返回子件
|
||||||
|
|
||||||
|
2. **边界测试:**
|
||||||
|
- 库存=0,所有订单应返回子件
|
||||||
|
- 库存充足(>=累计需求),所有订单返回部件
|
||||||
|
- 库存恰好等于累计需求,应返回部件
|
||||||
|
|
||||||
|
3. **异常测试:**
|
||||||
|
- 现存量工作表不存在 → 记录警告,所有订单返回子件
|
||||||
|
- 部件编码在现存量中不存在 → 记录警告,该订单返回子件
|
||||||
|
- 数量列为空或非数字 → 应有错误处理
|
||||||
|
|
||||||
|
### 数据验证
|
||||||
|
1. **累计需求计算:**
|
||||||
|
- 验证累计需求 = 订单数量 × BOM需求量 + 之前累计
|
||||||
|
- 验证每次处理后累计需求量正确更新
|
||||||
|
|
||||||
|
2. **错误报告:**
|
||||||
|
- 检查错误报告工作表是否生成
|
||||||
|
- 验证库存不足错误正确记录
|
||||||
|
- 验证警告正确记录
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 关键假设
|
||||||
|
1. **[现存量]工作表结构:**
|
||||||
|
- 表头在第3行
|
||||||
|
- 数据从第4行开始
|
||||||
|
- B列 = 物料编码
|
||||||
|
- J列 = 库存数量
|
||||||
|
|
||||||
|
2. **[产品型号]工作表结构:**
|
||||||
|
- A列 = 生产订单号
|
||||||
|
- B列 = 产品型号
|
||||||
|
- C列 = 数量
|
||||||
|
|
||||||
|
3. **BOM库[部件]工作表:**
|
||||||
|
- 必须包含"编码"列(部件编码)
|
||||||
|
- 必须包含"数量"列(BOM需求量)
|
||||||
|
|
||||||
|
### 已知限制
|
||||||
|
1. **库存检查时机:**
|
||||||
|
- 库存在初始化时加载一次
|
||||||
|
- 处理过程中库存不更新(不考虑库存增加)
|
||||||
|
|
||||||
|
2. **错误处理:**
|
||||||
|
- 未找到库存数据时返回 False(使用子件)
|
||||||
|
- 不会中断整个流程,继续处理下一个订单
|
||||||
|
|
||||||
|
3. **数量列处理:**
|
||||||
|
- 当前代码假设数量列总是存在且为数字
|
||||||
|
- 如果数量列为空或非数字,可能导致运行时错误
|
||||||
|
|
||||||
|
## 后续改进建议
|
||||||
|
|
||||||
|
1. **增强错误处理:**
|
||||||
|
- 在 `ReadInputModels` 中验证数量列是否存在
|
||||||
|
- 处理数量列为空或非数字的情况
|
||||||
|
|
||||||
|
2. **性能优化:**
|
||||||
|
- 如果库存数据很大,考虑只加载需要的部件编码
|
||||||
|
- 添加日志记录库存使用情况
|
||||||
|
|
||||||
|
3. **功能扩展:**
|
||||||
|
- 支持库存实时更新(如果需要)
|
||||||
|
- 支持按批次或其他维度分组计算需求
|
||||||
|
- 添加库存预留功能(预留库存给特定订单)
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
- BOM提取系统架构:`CLAUDE.md`
|
||||||
|
- 库存校验计划:原始计划文档
|
||||||
|
- 测试用例:需要单独创建
|
||||||
|
|
||||||
|
## 实施验证清单
|
||||||
|
- [x] M04_Config.bas 添加常量
|
||||||
|
- [x] M08_ComponentProcessor.bas 添加变量
|
||||||
|
- [x] M08_ComponentProcessor.bas 实现 LoadInventoryData
|
||||||
|
- [x] M08_ComponentProcessor.bas 重写 CheckComponentInventory
|
||||||
|
- [x] M08_ComponentProcessor.bas 添加 InitComponentProcessorWithInventory
|
||||||
|
- [x] M08_ComponentProcessor.bas 修改 ProcessComponentRecord 签名
|
||||||
|
- [x] M09_BOMExtractor.bas 修改 RunBOMExtraction(初始化、提取数量)
|
||||||
|
- [x] M09_BOMExtractor.bas 修改 ProcessSingleModel(添加参数)
|
||||||
|
- [x] M09_BOMExtractor.bas 修改 MatchAllMaterialTypesWithValidation(添加参数)
|
||||||
|
- [ ] 验证 ReadInputModels 读取3列(需要确认)
|
||||||
|
- [ ] 端到端测试
|
||||||
|
- [ ] 错误场景测试
|
||||||
Reference in New Issue
Block a user