feat: implement inventory comparison worksheet for component materials
All checks were successful
NTFY Notification / notify (push) Successful in 5s

Add new "库存比对" worksheet that displays component inventory status
alongside material requirements for production planning.

Changes:
- M04_Config: Add INVENTORY_COMPARISON_SHEET_NAME constant
- M08_ComponentProcessor: Enhance component processing to return both
  component and sub-components when inventory insufficient; add
  GetComponentInventoryInfo() read-only function for inventory lookup
- M09_BOMExtractor: Add WriteInventoryComparisonResults() function
  to generate inventory comparison worksheet with 7 columns showing
  material code, name, required quantity, stock quantity, and status

Output format:
- Columns: 序号, 生产订单号, 物料编码, 物料名称, 所需数量, 库存数量, 库存充足
- Color coding: Green (sufficient) / Red (insufficient)
- Sequence numbering: 10+0, 10+1 per order

Backward compatibility: Existing BOM提取结果 and BIP上传 worksheets
remain unchanged.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-26 17:55:20 +08:00
parent e086dee920
commit c696057cd4
3 changed files with 373 additions and 4 deletions

View File

@@ -28,6 +28,9 @@ Public Const BIP_ROW_NUMBER_BASE As Long = 7000
Public Const BIP_SUPPLY_MODE As String = "一般发料"
Public Const BIP_ISSUE_ORG As String = "重庆布莱迪仪器仪表有限公司"
' 库存比对工作表配置
Public Const INVENTORY_COMPARISON_SHEET_NAME As String = "库存比对"
' 输入列名称配置
Public Const INPUT_COL_MODEL As String = "型号"
Public Const INPUT_COL_PRODUCT_MODEL As String = "产品型号"

View File

@@ -174,16 +174,28 @@ Public Function ProcessComponentRecord( _
Set headerMap = M07_BOMMatcher.BuildWorksheetHeaderMap(wsComponent)
' 步骤4: 检查部件库存
Dim componentInfo As Object
If CheckComponentInventory(wsComponent, matchedRowNum, headerMap, orderQty, productionOrderNo) Then
' 库存充足,返回部件物料
Dim componentInfo As Object
Set componentInfo = ExtractComponentInfo(wsComponent, matchedRowNum, headerMap, "部件")
componentInfo("remarks") = ""
componentInfo("isStockSufficient") = True
If Not componentInfo Is Nothing Then
materials.Add componentInfo
End If
Else
' 库存不足,提取子件(接头+弹性元件)
' 【关键修复】库存不足时,同时返回部件(标记)和子件
' 步骤1: 返回部件信息(用于库存比对表)
Set componentInfo = ExtractComponentInfo(wsComponent, matchedRowNum, headerMap, "部件")
componentInfo("remarks") = "部件无库存,使用子件"
componentInfo("isStockSufficient") = False
If Not componentInfo Is Nothing Then
materials.Add componentInfo
End If
' 步骤2: 返回子件信息用于BOM提取结果和BIP上传
Dim subComponents As Collection
Set subComponents = ExtractSubComponents(wsComponent, matchedRowNum, headerMap)
@@ -405,6 +417,7 @@ Private Function ExtractComponentInfo( _
End If
componentInfo("remarks") = ""
componentInfo("isStockSufficient") = True ' 默认值,会在 ProcessComponentRecord 中被覆盖
Set ExtractComponentInfo = componentInfo
Exit Function
@@ -567,6 +580,87 @@ ErrorHandler:
Set ExtractSingleSubComponent = Nothing
End Function
' ------------------------------------------------------------------------------
' 获取部件库存信息(用于库存比对工作表)
'
' 输入:
' componentCode - 部件编码
' bomQty - BOM需求量单个产品
' orderQty - 订单数量
'
' 输出:
' Object - 库存信息字典
' .stockQty - 库存数量
' .requiredQty - 所需数量 (orderQty × bomQty)
' .accumulatedDemand - 累计需求量
' .totalDemand - 总需求量 (累计需求 + 当前需求)
' .isSufficient - 库存是否充足 (True/False)
'
' 注意:
' - 此函数是只读的,不会修改 g_AccumulatedDemandDict
' - 库存充足性判断基于累计需求量,但仅返回结果,不更新状态
' ------------------------------------------------------------------------------
Public Function GetComponentInventoryInfo( _
ByVal componentCode As String, _
ByVal bomQty As Long, _
ByVal orderQty As Long _
) As Object
On Error GoTo ErrorHandler
Dim invInfo As Object
Set invInfo = CreateObject("Scripting.Dictionary")
' 步骤1: 获取库存数量
Dim stockQty As Long
stockQty = 0 ' 默认值为0
If Not g_InventoryDict Is Nothing And g_InventoryDict.Exists(componentCode) Then
stockQty = g_InventoryDict(componentCode)
End If
' 步骤2: 计算需求量
Dim requiredQty As Long
requiredQty = orderQty * bomQty
Dim accumulatedDemand As Long
accumulatedDemand = 0
If Not g_AccumulatedDemandDict Is Nothing Then
If g_AccumulatedDemandDict.Exists(componentCode) Then
accumulatedDemand = g_AccumulatedDemandDict(componentCode)
End If
End If
Dim totalDemand As Long
totalDemand = accumulatedDemand + requiredQty
' 步骤3: 判断库存是否充足
Dim isSufficient As Boolean
isSufficient = (stockQty >= totalDemand)
' 步骤4: 返回库存信息
invInfo("stockQty") = stockQty
invInfo("requiredQty") = requiredQty
invInfo("accumulatedDemand") = accumulatedDemand
invInfo("totalDemand") = totalDemand
invInfo("isSufficient") = isSufficient
Set GetComponentInventoryInfo = invInfo
Exit Function
ErrorHandler:
' 出错时返回默认值(库存不足)
Dim errorInfo As Object
Set errorInfo = CreateObject("Scripting.Dictionary")
errorInfo("stockQty") = 0
errorInfo("requiredQty") = orderQty * bomQty
errorInfo("accumulatedDemand") = 0
errorInfo("totalDemand") = orderQty * bomQty
errorInfo("isSufficient") = False
Set GetComponentInventoryInfo = errorInfo
End Function
' ------------------------------------------------------------------------------
' 验证部件组合是否有效
'

View File

@@ -177,6 +177,17 @@ Public Function RunBOMExtraction() As String
GoTo ExitHandler
End If
' 步骤5.6: 生成库存比对工作表
Application.StatusBar = "正在生成库存比对数据..."
Dim wsInventoryCompare As Worksheet
Set wsInventoryCompare = WriteInventoryComparisonResults(allResults, inputModels)
If wsInventoryCompare Is Nothing Then
RunBOMExtraction = "错误:无法生成库存比对工作表。"
GoTo ExitHandler
End If
' 步骤6: 生成错误报告
If g_Logger.HasIssues Then
g_Logger.PrintReport ActiveWorkbook
@@ -205,9 +216,10 @@ Public Function RunBOMExtraction() As String
Dim msg As String
msg = "BOM提取完成" & vbCrLf & _
"处理型号数: " & totalModels & vbCrLf & _
"提取物料数: " & allResults.count & vbCrLf & _
"提取物料数: " & allResults.Count & vbCrLf & _
"成功数: " & successCount & vbCrLf & _
"异常数: " & errorCount
"异常数: " & errorCount & vbCrLf & _
"已生成库存比对工作表"
If g_Logger.HasErrors Then
msg = msg & vbCrLf & vbCrLf & "发现错误,已生成错误报告工作表。"
@@ -1364,6 +1376,266 @@ ErrorHandler:
Set WriteBIPUploadResults = Nothing
End Function
' ------------------------------------------------------------------------------
' 写入库存比对数据到工作表
'
' 输入:
' results - 提取结果集合来自BOM提取结果
' inputModels - 输入数据数组(包含订单号、型号、数量、产品编码)
'
' 输出:
' Worksheet - 库存比对工作表
'
' 逻辑:
' 1. 创建或清空"库存比对"工作表
' 2. 写入表头7列
' 3. 遍历results筛选materialType="部件"的物料
' 4. 对每个部件物料:
' a. 查找订单数量
' b. 调用M08_ComponentProcessor.GetComponentInventoryInfo()
' c. 计算序号
' d. 填充行数据
' 5. 批量写入数据
' 6. 格式化工作表(边框、列宽)
'
' 输出格式7列:
' 1: 序号10+0, 10+1, 20+0, 20+1...
' 2: 生产订单号
' 3: 物料编码
' 4: 物料名称
' 5: 所需数量(订单数量 × BOM数量
' 6: 库存数量
' 7: 库存充足("是"/"否"
' ------------------------------------------------------------------------------
Private Function WriteInventoryComparisonResults( _
ByVal results As Collection, _
ByVal inputModels As Variant _
) As Worksheet
On Error GoTo ErrorHandler
Dim ws As Worksheet
Dim headers As Variant
Dim c As Long
Dim modelToOrderMap As Object
Dim i As Long
Dim j As Long
Dim result As Variant
Dim orderNo As String
Dim modelStr As String
Dim componentResults As Collection
Dim materialType As String
Dim outputArr() As Variant
Dim baseSeqNum As Long
Dim materialSeqNum As Long
Dim currentOrderNo As String
Dim prevOrderNo As String
Dim resultOrderNo As String
Dim finalSeqNum As Long
Dim orderQty As Long
Dim materialCode As String
Dim materialName As String
Dim bomQty As Long
Dim invInfo As Object
Dim stockQty As Long
Dim isSufficientStr As String
Dim requiredQty As Long
Dim suffStatus As String
' 创建或获取工作表
On Error Resume Next
Set ws = ThisWorkbook.Sheets(INVENTORY_COMPARISON_SHEET_NAME)
On Error GoTo ErrorHandler
If ws Is Nothing Then
Set ws = ThisWorkbook.Worksheets.Add(After:=ActiveWorkbook.Sheets(ActiveWorkbook.Sheets.Count))
ws.Name = INVENTORY_COMPARISON_SHEET_NAME
Else
ws.Cells.Clear
End If
' 写入表头7列
headers = Array("序号", "生产订单号", "物料编码", "物料名称", "所需数量", "库存数量", "库存充足")
For c = 1 To 7
ws.Cells(1, c).Value = headers(c - 1)
Next c
' 格式化表头
With ws.Range("A1:G1")
.Font.Bold = True
.Interior.Color = RGB(217, 217, 217)
.HorizontalAlignment = xlCenter
End With
' ========================================
' 步骤1: 构建产品型号 → 生产订单号的映射
' ========================================
' 在BOM提取结果中result(1)(生产订单号)仅第一行有值,
' 但result(2)(原始产品型号)每行都有值
' 因此需要建立映射:产品型号 → 生产订单号
Set modelToOrderMap = CreateObject("Scripting.Dictionary")
For i = 1 To results.Count
result = results(i)
orderNo = CStr(result(1)) ' 第1列生产订单号
modelStr = CStr(result(2)) ' 第2列原始产品型号
' 仅当订单号不为空时,添加映射
If Len(Trim(orderNo)) > 0 And Len(Trim(modelStr)) > 0 Then
If Not modelToOrderMap.Exists(modelStr) Then
modelToOrderMap.Add modelStr, orderNo
End If
End If
Next i
' ========================================
' 步骤2: 筛选"部件"类型物料
' ========================================
Set componentResults = New Collection
For i = 1 To results.Count
result = results(i)
' 第9列是物料类型
materialType = CStr(result(9))
' 仅处理"部件"类型
If materialType = "部件" Then
componentResults.Add result
End If
Next i
' 写入数据
If componentResults.Count > 0 Then
' 先设置物料编码列第3列C列为文本格式
ws.Range("C2:C" & (componentResults.Count + 1)).NumberFormat = "@"
' 准备输出数组
ReDim outputArr(1 To componentResults.Count, 1 To 7)
' 序号生成变量
baseSeqNum = 0
materialSeqNum = 0
currentOrderNo = ""
prevOrderNo = ""
' ========================================
' 步骤3: 写入库存比对数据
' ========================================
For i = 1 To componentResults.Count
result = componentResults(i)
' 【关键修复】从映射表中获取订单号
modelStr = CStr(result(2)) ' 第2列原始产品型号
If modelToOrderMap.Exists(modelStr) Then
resultOrderNo = CStr(modelToOrderMap(modelStr))
Else
resultOrderNo = ""
End If
' 检测新订单
If resultOrderNo <> prevOrderNo And Len(Trim(resultOrderNo)) > 0 Then
prevOrderNo = resultOrderNo
currentOrderNo = resultOrderNo
baseSeqNum = baseSeqNum + 10
materialSeqNum = 0
End If
' 计算序号:基数 + 物料序号
finalSeqNum = baseSeqNum + materialSeqNum
materialSeqNum = materialSeqNum + 1
' 查找订单数量使用currentOrderNo
orderQty = 1 ' 默认值
For j = LBound(inputModels, 1) To UBound(inputModels, 1)
If CStr(inputModels(j, 1)) = currentOrderNo Then
orderQty = CLng(inputModels(j, 3)) ' 第3列是订单数量
Exit For
End If
Next j
' 提取物料信息
materialCode = CStr(result(11)) ' 第11列物料编码
materialName = CStr(result(10)) ' 第10列物料名称
' 第12列BOM库基础数量
If IsNumeric(result(12)) Then
bomQty = CLng(result(12))
Else
bomQty = 1
End If
' 获取库存信息
Set invInfo = M08_ComponentProcessor.GetComponentInventoryInfo( _
materialCode, bomQty, orderQty)
' 提取库存数据
stockQty = invInfo("stockQty")
isSufficientStr = IIf(invInfo("isSufficient"), "是", "否")
' 计算所需数量
requiredQty = orderQty * bomQty
' 填充输出数组
outputArr(i, 1) = finalSeqNum ' 序号
outputArr(i, 2) = currentOrderNo ' 【关键修复】生产订单号
outputArr(i, 3) = materialCode ' 物料编码
outputArr(i, 4) = materialName ' 物料名称
outputArr(i, 5) = requiredQty ' 所需数量
outputArr(i, 6) = stockQty ' 库存数量
outputArr(i, 7) = isSufficientStr ' 库存充足
Next i
' 批量写入数据
ws.Range("A2").Resize(componentResults.Count, 7).Value = outputArr
' 格式化数据区域
With ws.Range("A2:G" & (componentResults.Count + 1))
.Borders.LineStyle = xlContinuous
.Borders.Weight = xlThin
End With
' 根据库存状态设置背景色
For i = 1 To componentResults.Count
suffStatus = CStr(outputArr(i, 7))
If suffStatus = "否" Then
' 库存不足,标记为淡红色
ws.Cells(i + 1, 7).Interior.Color = RGB(255, 200, 200)
Else
' 库存充足,标记为淡绿色
ws.Cells(i + 1, 7).Interior.Color = RGB(200, 255, 200)
End If
Next i
' 设置列宽
ws.Columns("A").ColumnWidth = 8 ' 序号
ws.Columns("B").ColumnWidth = 15 ' 生产订单号
ws.Columns("C").ColumnWidth = 15 ' 物料编码
ws.Columns("D").ColumnWidth = 30 ' 物料名称
ws.Columns("E").ColumnWidth = 10 ' 所需数量
ws.Columns("F").ColumnWidth = 10 ' 库存数量
ws.Columns("G").ColumnWidth = 10 ' 库存充足
' 冻结首行
ws.Activate
ActiveWindow.FreezePanes = False
ws.Rows(2).Select
ActiveWindow.FreezePanes = True
ws.Cells(1, 1).Select
End If
Set WriteInventoryComparisonResults = ws
Exit Function
ErrorHandler:
Set WriteInventoryComparisonResults = Nothing
End Function
' ------------------------------------------------------------------------------
' 打开BOM库文件
'