From c696057cd4abab43bcd2baf04e07478795d87ac8 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Thu, 26 Feb 2026 17:55:20 +0800 Subject: [PATCH] feat: implement inventory comparison worksheet for component materials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- VBA_BOMConverter/Modules/M04_Config.bas | 3 + .../Modules/M08_ComponentProcessor.bas | 98 ++++++- VBA_BOMConverter/Modules/M09_BOMExtractor.bas | 276 +++++++++++++++++- 3 files changed, 373 insertions(+), 4 deletions(-) diff --git a/VBA_BOMConverter/Modules/M04_Config.bas b/VBA_BOMConverter/Modules/M04_Config.bas index aab3d10..b9a1e38 100644 --- a/VBA_BOMConverter/Modules/M04_Config.bas +++ b/VBA_BOMConverter/Modules/M04_Config.bas @@ -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 = "产品型号" diff --git a/VBA_BOMConverter/Modules/M08_ComponentProcessor.bas b/VBA_BOMConverter/Modules/M08_ComponentProcessor.bas index 5e3831a..5ab34d4 100644 --- a/VBA_BOMConverter/Modules/M08_ComponentProcessor.bas +++ b/VBA_BOMConverter/Modules/M08_ComponentProcessor.bas @@ -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 + ' ------------------------------------------------------------------------------ ' 验证部件组合是否有效 ' diff --git a/VBA_BOMConverter/Modules/M09_BOMExtractor.bas b/VBA_BOMConverter/Modules/M09_BOMExtractor.bas index 7a2556d..b664ee0 100644 --- a/VBA_BOMConverter/Modules/M09_BOMExtractor.bas +++ b/VBA_BOMConverter/Modules/M09_BOMExtractor.bas @@ -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库文件 '