feat: integrate inventory verification for component materials in BOM extraction
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:
Misaka_Company
2026-02-24 16:21:18 +08:00
parent 24a54bd1ea
commit 8477792d41
4 changed files with 452 additions and 28 deletions

View File

@@ -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_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库条件字段列表所有可能的条件字段
Public Function GetBOMConditionFields() As Variant
GetBOMConditionFields = Array( _

View File

@@ -23,6 +23,10 @@ Option Explicit
' 模块级变量 - 错误记录器
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
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 params As Object, _
ByVal logger As clsErrorLogger, _
ByVal matchedRowNum As Long _
ByVal matchedRowNum As Long, _
ByVal orderQty As Long _
) As Collection
On Error GoTo ErrorHandler
@@ -88,7 +173,7 @@ Public Function ProcessComponentRecord( _
Set headerMap = M07_BOMMatcher.BuildWorksheetHeaderMap(wsComponent)
' 步骤4: 检查部件库存
If CheckComponentInventory(wsComponent, matchedRowNum, headerMap) Then
If CheckComponentInventory(wsComponent, matchedRowNum, headerMap, orderQty) Then
' 库存充足,返回部件物料
Dim componentInfo As Object
Set componentInfo = ExtractComponentInfo(wsComponent, matchedRowNum, headerMap, "部件")
@@ -131,39 +216,126 @@ ErrorHandler:
End Function
' ------------------------------------------------------------------------------
' 检查部件库存状态(预留接口
' 检查部件库存状态(集成库存校验
'
' 输入:
' wsComponent - "部件"工作表
' rowNum - 匹配到的行号
' headerMap - 表头映射
' orderQty - 订单数量
'
' 输出:
' Boolean - True表示库存False表示库存
' Boolean - True表示库存充足False表示库存不足
'
' 注意:
' - 当前版本默认返回True库存充足
' - 预留接口未来可连接ERP/库存系统
' - 可扩展为查询库存Excel表或API
' 逻辑:
' 1. 提取部件编码
' 2. 获取BOM需求量部件工作表中的数量列
' 3. 检查库存数据是否存在
' 4. 计算累计需求量 = 订单数量 × BOM需求量 + 之前累计需求
' 5. 比较库存和需求,更新累计需求量
'
' 示例:
' 订单1、订单3、订单6都用部件A每个订单数量=2BOM需求量=1现存量=5
' - 订单1累计需求 = 2×1 + 0 = 25 >= 2 ✓ true使用部件
' - 订单3累计需求 = 2×1 + 2 = 45 >= 4 ✓ true使用部件
' - 订单6累计需求 = 2×1 + 4 = 65 < 6 ✗ false使用子件
' ------------------------------------------------------------------------------
Private Function CheckComponentInventory( _
ByVal wsComponent As Worksheet, _
ByVal rowNum As Long, _
ByVal headerMap As Object _
ByVal headerMap As Object, _
ByVal orderQty As Long _
) As Boolean
' TODO: 连接库存系统查询实际库存
' 当前版本默认返回True库存充足
On Error GoTo ErrorHandler
' 示例扩展代码(注释):
' If headerMap.Exists("库存数量") Then
' Dim stockQty As Long
' stockQty = CLng(wsComponent.Cells(rowNum, headerMap("库存数量")).Value)
' CheckComponentInventory = (stockQty > 0)
' Else
' CheckComponentInventory = True
' End If
' 步骤1: 提取部件编码
Dim codeCol As Long
Dim codeKey As String
codeKey = LCase(BOMLIB_COL_CODE)
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
' ------------------------------------------------------------------------------

View File

@@ -93,7 +93,11 @@ Public Function RunBOMExtraction() As String
' 初始化各模块
M06_ModelParser.InitModelParser 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
@@ -129,12 +133,14 @@ Public Function RunBOMExtraction() As String
' 处理单个型号
Dim productionOrderNo As String
Dim modelString As String
Dim orderQty As Long
productionOrderNo = CStr(inputModels(i, 1)) ' Column A: 生产订单号
modelString = CStr(inputModels(i, 2)) ' Column B: 产品型号
orderQty = CLng(inputModels(i, 3)) ' Column C: 数量
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
@@ -295,9 +301,9 @@ Private Function ReadInputModels(ByVal ws As Worksheet) As Variant
Exit Function
End If
' 读取数据到数组(返回2列:生产订单号、产品型号)
' 假设生产订单号在A列列1产品型号在colIdx列
ReadInputModels = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, colIdx)).Value
' 读取数据到数组(返回3列:生产订单号、产品型号、数量
' 假设A列(1)=生产订单号, B列(2)=产品型号, C列(3)=数量
ReadInputModels = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 3)).Value
Exit Function
ErrorHandler:
@@ -326,7 +332,8 @@ Private Function ProcessSingleModel( _
ByVal modelString As String, _
ByVal bomWb As Workbook, _
ByVal logger As clsErrorLogger, _
ByVal productionOrderNo As String _
ByVal productionOrderNo As String, _
ByVal orderQty As Long _
) As Collection
On Error GoTo ErrorHandler
@@ -348,7 +355,7 @@ Private Function ProcessSingleModel( _
' 步骤2: 匹配所有物料类型(两阶段)
Dim allMaterials As Collection
Dim validation As Object
Set allMaterials = MatchAllMaterialTypesWithValidation(params, bomWb, logger, validation)
Set allMaterials = MatchAllMaterialTypesWithValidation(params, bomWb, logger, orderQty, validation)
' 步骤3: 生成输出行
Dim remarks As String
@@ -414,6 +421,7 @@ Private Function MatchAllMaterialTypesWithValidation( _
ByVal params As Object, _
ByVal bomWb As Workbook, _
ByVal logger As clsErrorLogger, _
ByVal orderQty As Long, _
ByRef outValidation As Object _
) As Collection
On Error GoTo ErrorHandler
@@ -457,7 +465,7 @@ Private Function MatchAllMaterialTypesWithValidation( _
If bomMatchResult("success") Then
Set componentMaterials = M08_ComponentProcessor.ProcessComponentRecord( _
ws, params, logger, bomMatchResult("rowNums")(1))
ws, params, logger, bomMatchResult("rowNums")(1), orderQty)
Debug.Print " -> 返回物料数: " & componentMaterials.count
End If