Pārlūkot izejas kodu

库存后端提交

Gogs 3 mēneši atpakaļ
vecāks
revīzija
f55df0e778

+ 141 - 48
dtm-system/src/main/java/com/dtm/storage/service/InventoryService.java

@@ -143,7 +143,9 @@ public class InventoryService {
         Set<String> finishedSkus = getFinishedSkus(productInfos);
 
         Map<String, double[]> purchaseSummary = new HashMap<>();
+        Map<String, double[]> finishedPurchaseSummary = new HashMap<>();
         Map<String, Double> salesSummary = new HashMap<>();
+        Map<String, Double> finishedSalesSummary = new HashMap<>();
         Map<YearMonth, Double> purchaseMonthly = new TreeMap<>();
         Map<YearMonth, Double> salesMonthly = new TreeMap<>();
 
@@ -153,38 +155,46 @@ public class InventoryService {
 
         for (PurchaseRecord record : purchaseRecords) {
             String code = record.getProductCode();
-            if (!shouldIncludeSku(code, finishedSkus)) {
+            if (code == null || code.trim().isEmpty()) {
                 continue;
             }
-            totalPurchaseQtyRaw += record.getQuantity();
-            totalPurchaseAmountRaw += record.getAmount();
             double[] agg = purchaseSummary.computeIfAbsent(code, key -> new double[2]);
             agg[0] += record.getQuantity();
             agg[1] += record.getAmount();
 
-            LocalDate date = record.getDate();
-            if (date != null) {
-                purchaseMonthly.merge(YearMonth.from(date), record.getQuantity(), Double::sum);
+            if (shouldIncludeSku(code, finishedSkus)) {
+                totalPurchaseQtyRaw += record.getQuantity();
+                totalPurchaseAmountRaw += record.getAmount();
+                double[] finishedAgg = finishedPurchaseSummary.computeIfAbsent(code, key -> new double[2]);
+                finishedAgg[0] += record.getQuantity();
+                finishedAgg[1] += record.getAmount();
+                LocalDate date = record.getDate();
+                if (date != null) {
+                    purchaseMonthly.merge(YearMonth.from(date), record.getQuantity(), Double::sum);
+                }
             }
         }
 
         for (SalesRecord record : salesRecords) {
             String code = record.getProductCode();
-            if (!shouldIncludeSku(code, finishedSkus)) {
+            if (code == null || code.trim().isEmpty()) {
                 continue;
             }
-            totalSalesQtyRaw += record.getQuantity();
             salesSummary.merge(code, record.getQuantity(), Double::sum);
-
-            LocalDate date = record.getDate();
-            if (date != null) {
-                salesMonthly.merge(YearMonth.from(date), record.getQuantity(), Double::sum);
+            if (shouldIncludeSku(code, finishedSkus)) {
+                totalSalesQtyRaw += record.getQuantity();
+                finishedSalesSummary.merge(code, record.getQuantity(), Double::sum);
+                LocalDate date = record.getDate();
+                if (date != null) {
+                    salesMonthly.merge(YearMonth.from(date), record.getQuantity(), Double::sum);
+                }
             }
         }
 
         int totalPurchaseQty = (int) Math.round(totalPurchaseQtyRaw);
         int totalSalesQty = (int) Math.round(totalSalesQtyRaw);
-        int assemblyQty = calculateAssemblyQuantity(finishedSkus);
+        AssemblyAggregation assemblyAggregation = buildAssemblyAggregation(finishedSkus);
+        int assemblyQty = assemblyAggregation.totalQty;
         int totalInventory = totalPurchaseQty + assemblyQty - totalSalesQty;
         double totalValue = Math.round((totalPurchaseAmountRaw / 10000.0) * 100.0) / 100.0;
         double avgInventory = totalInventory > 0 ? totalInventory / 2.0 : 1.0;
@@ -200,19 +210,34 @@ public class InventoryService {
         overview.put("salesQty", totalSalesQty);
         overview.put("assemblyQty", assemblyQty);
 
-        Map<String, Object> monthlyComparison = buildMonthlyComparison(purchaseMonthly, salesMonthly);
-        List<Map<String, Object>> skuSummary = buildSkuSummary(purchaseSummary, salesSummary, productInfoMap);
-        List<Map<String, Object>> spuSummary = buildSpuSummary(purchaseSummary, salesSummary, productInfoMap);
+        Map<String, Object> monthlyComparison = buildMonthlyComparison(purchaseMonthly, salesMonthly, assemblyAggregation.monthly);
+        List<Map<String, Object>> skuSummary = buildSkuSummary(
+                purchaseSummary,
+                salesSummary,
+                productInfoMap,
+                assemblyAggregation.outputBySku,
+                assemblyAggregation.consumeBySku
+        );
+        List<Map<String, Object>> spuSummary = buildSpuSummary(
+                finishedPurchaseSummary,
+                finishedSalesSummary,
+                productInfoMap,
+                assemblyAggregation.outputBySku
+        );
 
         dataLoader.clearCache();
         return new InventorySnapshot(overview, monthlyComparison, skuSummary, spuSummary);
     }
 
     private Map<String, Object> buildMonthlyComparison(Map<YearMonth, Double> purchaseMonthly,
-                                                       Map<YearMonth, Double> salesMonthly) {
+                                                       Map<YearMonth, Double> salesMonthly,
+                                                       Map<YearMonth, Double> assemblyMonthly) {
         Set<YearMonth> allMonths = new HashSet<>();
         allMonths.addAll(purchaseMonthly.keySet());
         allMonths.addAll(salesMonthly.keySet());
+        if (assemblyMonthly != null) {
+            allMonths.addAll(assemblyMonthly.keySet());
+        }
         List<YearMonth> sorted = new ArrayList<>(allMonths);
         Collections.sort(sorted);
 
@@ -225,7 +250,8 @@ public class InventoryService {
         for (YearMonth month : sorted) {
             double purchaseQty = purchaseMonthly.getOrDefault(month, 0.0);
             double salesQty = salesMonthly.getOrDefault(month, 0.0);
-            cumulativeInventory += (int) Math.round(purchaseQty - salesQty);
+            double assemblyQty = assemblyMonthly == null ? 0.0 : assemblyMonthly.getOrDefault(month, 0.0);
+            cumulativeInventory += (int) Math.round(purchaseQty + assemblyQty - salesQty);
 
             months.add(month.toString());
             purchase.add((int) Math.round(purchaseQty));
@@ -243,16 +269,26 @@ public class InventoryService {
 
     private List<Map<String, Object>> buildSkuSummary(Map<String, double[]> purchaseSummary,
                                                       Map<String, Double> salesSummary,
-                                                      Map<String, ProductInfo> productInfoMap) {
+                                                      Map<String, ProductInfo> productInfoMap,
+                                                      Map<String, Double> assemblyOutputBySku,
+                                                      Map<String, Double> assemblyConsumeBySku) {
         double totalAmount = purchaseSummary.values().stream().mapToDouble(v -> v[1]).sum();
+        Set<String> allSkus = new HashSet<>();
+        allSkus.addAll(purchaseSummary.keySet());
+        allSkus.addAll(salesSummary.keySet());
+        allSkus.addAll(assemblyOutputBySku.keySet());
+        allSkus.addAll(assemblyConsumeBySku.keySet());
+
         List<Map<String, Object>> result = new ArrayList<>();
-        for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
-            String sku = entry.getKey();
-            double purchaseQty = entry.getValue()[0];
-            double purchaseAmount = entry.getValue()[1];
+        for (String sku : allSkus) {
+            double[] purchase = purchaseSummary.getOrDefault(sku, new double[2]);
+            double purchaseQty = purchase[0];
+            double purchaseAmount = purchase[1];
             double salesQty = salesSummary.getOrDefault(sku, 0.0);
+            double assemblyOutput = assemblyOutputBySku.getOrDefault(sku, 0.0);
+            double assemblyConsume = assemblyConsumeBySku.getOrDefault(sku, 0.0);
 
-            double inventory = purchaseQty - salesQty;
+            double inventory = purchaseQty + assemblyOutput - salesQty - assemblyConsume;
             double amountRatio = totalAmount > 0 ? (purchaseAmount / totalAmount) * 100 : 0;
             double avgInventory = (purchaseQty + inventory) / 2.0;
             double turnoverRate = avgInventory > 0 ? salesQty / avgInventory : 0;
@@ -260,6 +296,7 @@ public class InventoryService {
             ProductInfo info = productInfoMap.get(sku);
             Map<String, Object> row = new LinkedHashMap<>();
             row.put("sku", sku);
+            row.put("category", info != null ? info.getCategory() : "");
             row.put("attribute", info != null ? info.getAttribute() : "");
             row.put("spuName", info != null ? info.getSpuName() : "");
             row.put("purchaseQty", (int) Math.round(purchaseQty));
@@ -272,15 +309,13 @@ public class InventoryService {
         }
 
         result.sort(Comparator.comparing((Map<String, Object> row) -> ((Number) row.getOrDefault("purchaseQty", 0)).doubleValue()).reversed());
-        if (result.size() > 20) {
-            return new ArrayList<>(result.subList(0, 20));
-        }
         return result;
     }
 
     private List<Map<String, Object>> buildSpuSummary(Map<String, double[]> purchaseSummary,
                                                       Map<String, Double> salesSummary,
-                                                      Map<String, ProductInfo> productInfoMap) {
+                                                      Map<String, ProductInfo> productInfoMap,
+                                                      Map<String, Double> assemblyOutputBySku) {
         Map<String, SpuAggregate> aggregates = new HashMap<>();
         for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
             String sku = entry.getKey();
@@ -292,6 +327,7 @@ public class InventoryService {
             agg.purchaseQty += entry.getValue()[0];
             agg.purchaseAmount += entry.getValue()[1];
             agg.salesQty += salesSummary.getOrDefault(sku, 0.0);
+            agg.assemblyQty += assemblyOutputBySku.getOrDefault(sku, 0.0);
             agg.skuCount += 1;
             if (info != null && info.getAttribute() != null && !info.getAttribute().trim().isEmpty()) {
                 String attr = info.getAttribute().trim();
@@ -304,7 +340,7 @@ public class InventoryService {
         for (Map.Entry<String, SpuAggregate> entry : aggregates.entrySet()) {
             String spu = entry.getKey();
             SpuAggregate agg = entry.getValue();
-            double inventory = agg.purchaseQty - agg.salesQty;
+            double inventory = agg.purchaseQty + agg.assemblyQty - agg.salesQty;
             double amountRatio = totalAmount > 0 ? (agg.purchaseAmount / totalAmount) * 100 : 0;
             double avgInventory = (agg.purchaseQty + inventory) / 2.0;
             double turnoverRate = avgInventory > 0 ? agg.salesQty / avgInventory : 0;
@@ -326,37 +362,57 @@ public class InventoryService {
         return result;
     }
 
-    private int calculateAssemblyQuantity(Set<String> finishedSkus) {
+    private AssemblyAggregation buildAssemblyAggregation(Set<String> finishedSkus) {
         List<AssemblyRecord> assemblyRecords = dataLoader.getAssemblyRecords();
+        Map<YearMonth, Double> monthly = new TreeMap<>();
+        Map<String, Double> outputBySku = new HashMap<>();
+        Map<String, Double> consumeBySku = new HashMap<>();
         if (assemblyRecords.isEmpty()) {
-            return 0;
+            return new AssemblyAggregation(0, monthly, outputBySku, consumeBySku);
         }
         Map<String, Set<String>> semiToFinished = buildSemiToFinishedMap();
-        if (semiToFinished.isEmpty() || finishedSkus == null || finishedSkus.isEmpty()) {
-            return (int) Math.round(assemblyRecords.stream().mapToDouble(AssemblyRecord::getQuantity).sum());
-        }
+        Set<String> safeFinishedSkus = finishedSkus == null ? Collections.emptySet() : finishedSkus;
         double total = 0.0;
         for (AssemblyRecord record : assemblyRecords) {
+            if (record == null) {
+                continue;
+            }
+            double qty = record.getQuantity();
+            if (qty <= 0) {
+                continue;
+            }
+            LocalDate date = record.getDate();
             String code = record.getProductCode();
             if (code == null || code.trim().isEmpty()) {
                 continue;
             }
-            boolean counted = false;
-            if (finishedSkus.contains(code)) {
-                total += record.getQuantity();
-                counted = true;
-            }
-            if (!counted) {
-                Set<String> finished = semiToFinished.get(code);
-                if (finished != null && !finished.isEmpty()) {
-                    boolean match = finished.stream().anyMatch(finishedSkus::contains);
-                    if (match) {
-                        total += record.getQuantity();
+            if (safeFinishedSkus.contains(code)) {
+                total += qty;
+                outputBySku.merge(code, qty, Double::sum);
+                if (date != null) {
+                    monthly.merge(YearMonth.from(date), qty, Double::sum);
+                }
+            } else {
+                Set<String> mappedFinished = semiToFinished.get(code);
+                if (mappedFinished != null && !mappedFinished.isEmpty()) {
+                    for (String finished : mappedFinished) {
+                        if (finished == null || finished.trim().isEmpty()) {
+                            continue;
+                        }
+                        if (!safeFinishedSkus.isEmpty() && !safeFinishedSkus.contains(finished)) {
+                            continue;
+                        }
+                        total += qty;
+                        outputBySku.merge(finished, qty, Double::sum);
+                        if (date != null) {
+                            monthly.merge(YearMonth.from(date), qty, Double::sum);
+                        }
                     }
                 }
+                consumeBySku.merge(code, qty, Double::sum);
             }
         }
-        return (int) Math.round(total);
+        return new AssemblyAggregation((int) Math.round(total), monthly, outputBySku, consumeBySku);
     }
 
     private Map<String, Set<String>> buildSemiToFinishedMap() {
@@ -420,8 +476,8 @@ public class InventoryService {
                 finished.add(code);
                 continue;
             }
-            String text = category.toLowerCase(Locale.ROOT);
-            if (text.contains("鍗婃垚") || text.contains("杈呮枡") || text.contains("閰嶄欢")) {
+            String text = category.replace(" ", "").toLowerCase(Locale.ROOT);
+            if (isExcludedCategory(text)) {
                 continue;
             }
             finished.add(code);
@@ -429,6 +485,25 @@ public class InventoryService {
         return finished;
     }
 
+    private boolean isExcludedCategory(String categoryText) {
+        if (categoryText == null || categoryText.isEmpty()) {
+            return false;
+        }
+        String[] keywords = {
+                "\u534a\u6210\u54c1", // 半成品
+                "\u8f85\u6599",       // 辅料
+                "\u914d\u4ef6",       // 配件
+                "\u9644\u4ef6",       // 附件
+                "\u8d60\u54c1"        // 赠品
+        };
+        for (String keyword : keywords) {
+            if (categoryText.contains(keyword)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean shouldIncludeSku(String code, Set<String> finishedSkus) {
         if (code == null || code.trim().isEmpty()) {
             return false;
@@ -481,6 +556,7 @@ public class InventoryService {
         double purchaseQty;
         double purchaseAmount;
         double salesQty;
+        double assemblyQty;
         int skuCount;
         Map<String, Integer> attributeCount = new HashMap<>();
 
@@ -511,4 +587,21 @@ public class InventoryService {
             this.spuSummary = spuSummary;
         }
     }
+
+    private static class AssemblyAggregation {
+        private final int totalQty;
+        private final Map<YearMonth, Double> monthly;
+        private final Map<String, Double> outputBySku;
+        private final Map<String, Double> consumeBySku;
+
+        private AssemblyAggregation(int totalQty,
+                                    Map<YearMonth, Double> monthly,
+                                    Map<String, Double> outputBySku,
+                                    Map<String, Double> consumeBySku) {
+            this.totalQty = totalQty;
+            this.monthly = monthly;
+            this.outputBySku = outputBySku;
+            this.consumeBySku = consumeBySku;
+        }
+    }
 }