feat: implement inventory comparison worksheet for component materials
All checks were successful
NTFY Notification / notify (push) Successful in 5s
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:
@@ -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 = "产品型号"
|
||||
|
||||
@@ -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
|
||||
|
||||
' ------------------------------------------------------------------------------
|
||||
' 验证部件组合是否有效
|
||||
'
|
||||
|
||||
@@ -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库文件
|
||||
'
|
||||
|
||||
Reference in New Issue
Block a user