Răsfoiți Sursa

库存后端提交

Gogs 2 luni în urmă
părinte
comite
b775a7bb48

+ 123 - 169
dtm-system/src/main/java/com/dtm/storage/service/InventoryService.java

@@ -25,22 +25,46 @@ import java.util.stream.Collectors;
 @Service
 public class InventoryService {
     private final StorageDataLoader dataLoader;
-    private volatile InventorySnapshot snapshot;
 
     public InventoryService(StorageDataLoader dataLoader) {
         this.dataLoader = dataLoader;
     }
 
-    public void invalidateCache() {
-        snapshot = null;
-    }
+    public Map<String, Object> getOverviewData() {
+        List<PurchaseRecord> purchaseRecords = dataLoader.getPurchaseRecords();
+        List<SalesRecord> salesRecords = dataLoader.getSalesRecords();
+        Set<String> finishedSkus = getFinishedSkus();
+
+        if (!finishedSkus.isEmpty()) {
+            purchaseRecords = purchaseRecords.stream()
+                    .filter(r -> finishedSkus.contains(r.getProductCode()))
+                    .collect(Collectors.toList());
+            salesRecords = salesRecords.stream()
+                    .filter(r -> finishedSkus.contains(r.getProductCode()))
+                    .collect(Collectors.toList());
+        }
 
-    public void warmCache() {
-        ensureSnapshot();
-    }
+        int totalPurchaseQty = (int) Math.round(purchaseRecords.stream().mapToDouble(PurchaseRecord::getQuantity).sum());
+        int totalSalesQty = (int) Math.round(salesRecords.stream().mapToDouble(SalesRecord::getQuantity).sum());
+        int assemblyQty = calculateAssemblyQuantity(finishedSkus);
 
-    public Map<String, Object> getOverviewData() {
-        return new LinkedHashMap<>(ensureSnapshot().overviewData);
+        int totalInventory = totalPurchaseQty + assemblyQty - totalSalesQty;
+        double totalValue = purchaseRecords.stream().mapToDouble(PurchaseRecord::getAmount).sum() / 10000.0;
+        totalValue = Math.round(totalValue * 100.0) / 100.0;
+
+        double avgInventory = totalInventory > 0 ? totalInventory / 2.0 : 1.0;
+        double turnoverRate = avgInventory > 0 ? (totalSalesQty / avgInventory) : 0.0;
+        turnoverRate = Math.round(turnoverRate * 100.0) / 100.0;
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("totalInventory", totalInventory);
+        result.put("totalValue", totalValue);
+        result.put("turnoverRate", turnoverRate);
+        result.put("inTransitRatio", 12.5);
+        result.put("purchaseQty", totalPurchaseQty);
+        result.put("salesQty", totalSalesQty);
+        result.put("assemblyQty", assemblyQty);
+        return result;
     }
 
     public Map<String, Object> getHealthIndex() {
@@ -65,12 +89,12 @@ public class InventoryService {
     public Map<String, Object> getLifecycleDistribution() {
         Map<String, Object> result = new LinkedHashMap<>();
         Map<String, Integer> finished = new LinkedHashMap<>();
-        finished.put("入期", 3200);
+        finished.put("入期", 3200);
         finished.put("成长期", 8500);
         finished.put("成熟期", 12800);
         finished.put("衰退期", 4060);
         Map<String, Integer> semi = new LinkedHashMap<>();
-        semi.put("入期", 2100);
+        semi.put("入期", 2100);
         semi.put("成长期", 5800);
         semi.put("成熟期", 7200);
         semi.put("衰退期", 2020);
@@ -87,136 +111,9 @@ public class InventoryService {
     }
 
     public Map<String, Object> getMonthlyComparisonData() {
-        return new LinkedHashMap<>(ensureSnapshot().monthlyComparisonData);
-    }
-
-    public List<Map<String, Object>> getSkuSummaryTable() {
-        return new ArrayList<>(ensureSnapshot().skuSummaryTable);
-    }
-
-    public List<Map<String, Object>> getSpuSummaryTable() {
-        return new ArrayList<>(ensureSnapshot().spuSummaryTable);
-    }
-
-    public List<Map<String, Object>> getMonthlyTurnoverTable() {
-        return Collections.emptyList();
-    }
-
-    public Map<String, Object> getSettings() {
-        Map<String, Object> result = new LinkedHashMap<>();
-        result.put("data", StorageSettings.getAnalysisWeights());
-        result.put("defaults", StorageSettings.DEFAULT_ANALYSIS_WEIGHTS);
-        return result;
-    }
-
-    public Map<String, Object> updateSettings(Map<String, Object> payload) {
-        StorageSettings.updateAnalysisWeights(payload);
-        invalidateCache();
-        Map<String, Object> result = new LinkedHashMap<>();
-        result.put("data", StorageSettings.getAnalysisWeights());
-        return result;
-    }
-
-    private InventorySnapshot ensureSnapshot() {
-        InventorySnapshot local = snapshot;
-        if (local != null) {
-            return local;
-        }
-        synchronized (this) {
-            local = snapshot;
-            if (local == null) {
-                local = buildSnapshot();
-                snapshot = local;
-            }
-            return local;
-        }
-    }
-
-    private InventorySnapshot buildSnapshot() {
         List<PurchaseRecord> purchaseRecords = dataLoader.getPurchaseRecords();
         List<SalesRecord> salesRecords = dataLoader.getSalesRecords();
-        List<ProductInfo> productInfos = dataLoader.getProductInfo();
-        Map<String, ProductInfo> productInfoMap = productInfos.stream()
-                .filter(info -> info.getProductCode() != null && !info.getProductCode().trim().isEmpty())
-                .collect(Collectors.toMap(ProductInfo::getProductCode, info -> info, (a, b) -> a));
-        Set<String> finishedSkus = getFinishedSkus(productInfos);
-
-        List<PurchaseRecord> filteredPurchase = filterPurchaseRecords(purchaseRecords, finishedSkus);
-        List<SalesRecord> filteredSales = filterSalesRecords(salesRecords, finishedSkus);
-        Map<String, double[]> purchaseSummary = summarizePurchases(filteredPurchase);
-        Map<String, Double> salesSummary = summarizeSales(filteredSales);
-
-        return new InventorySnapshot(
-                buildOverviewData(filteredPurchase, filteredSales, finishedSkus),
-                buildMonthlyComparisonData(filteredPurchase, filteredSales),
-                buildSkuSummaryTable(purchaseSummary, salesSummary, productInfoMap),
-                buildSpuSummaryTable(purchaseSummary, salesSummary, productInfoMap)
-        );
-    }
-
-    private List<PurchaseRecord> filterPurchaseRecords(List<PurchaseRecord> purchaseRecords, Set<String> finishedSkus) {
-        if (finishedSkus == null || finishedSkus.isEmpty()) {
-            return purchaseRecords;
-        }
-        return purchaseRecords.stream()
-                .filter(r -> finishedSkus.contains(r.getProductCode()))
-                .collect(Collectors.toList());
-    }
-
-    private List<SalesRecord> filterSalesRecords(List<SalesRecord> salesRecords, Set<String> finishedSkus) {
-        if (finishedSkus == null || finishedSkus.isEmpty()) {
-            return salesRecords;
-        }
-        return salesRecords.stream()
-                .filter(r -> finishedSkus.contains(r.getProductCode()))
-                .collect(Collectors.toList());
-    }
 
-    private Map<String, double[]> summarizePurchases(List<PurchaseRecord> purchaseRecords) {
-        Map<String, double[]> purchaseSummary = new HashMap<>();
-        for (PurchaseRecord record : purchaseRecords) {
-            double[] agg = purchaseSummary.computeIfAbsent(record.getProductCode(), k -> new double[2]);
-            agg[0] += record.getQuantity();
-            agg[1] += record.getAmount();
-        }
-        return purchaseSummary;
-    }
-
-    private Map<String, Double> summarizeSales(List<SalesRecord> salesRecords) {
-        Map<String, Double> salesSummary = new HashMap<>();
-        for (SalesRecord record : salesRecords) {
-            salesSummary.merge(record.getProductCode(), record.getQuantity(), Double::sum);
-        }
-        return salesSummary;
-    }
-
-    private Map<String, Object> buildOverviewData(List<PurchaseRecord> purchaseRecords,
-                                                  List<SalesRecord> salesRecords,
-                                                  Set<String> finishedSkus) {
-        int totalPurchaseQty = (int) Math.round(purchaseRecords.stream().mapToDouble(PurchaseRecord::getQuantity).sum());
-        int totalSalesQty = (int) Math.round(salesRecords.stream().mapToDouble(SalesRecord::getQuantity).sum());
-        int assemblyQty = calculateAssemblyQuantity(finishedSkus);
-
-        int totalInventory = totalPurchaseQty + assemblyQty - totalSalesQty;
-        double totalValue = purchaseRecords.stream().mapToDouble(PurchaseRecord::getAmount).sum() / 10000.0;
-        totalValue = Math.round(totalValue * 100.0) / 100.0;
-
-        double avgInventory = totalInventory > 0 ? totalInventory / 2.0 : 1.0;
-        double turnoverRate = avgInventory > 0 ? (totalSalesQty / avgInventory) : 0.0;
-        turnoverRate = Math.round(turnoverRate * 100.0) / 100.0;
-
-        Map<String, Object> result = new LinkedHashMap<>();
-        result.put("totalInventory", totalInventory);
-        result.put("totalValue", totalValue);
-        result.put("turnoverRate", turnoverRate);
-        result.put("inTransitRatio", 12.5);
-        result.put("purchaseQty", totalPurchaseQty);
-        result.put("salesQty", totalSalesQty);
-        result.put("assemblyQty", assemblyQty);
-        return result;
-    }
-
-    private Map<String, Object> buildMonthlyComparisonData(List<PurchaseRecord> purchaseRecords, List<SalesRecord> salesRecords) {
         Map<YearMonth, Double> purchaseMonthly = new TreeMap<>();
         for (PurchaseRecord record : purchaseRecords) {
             LocalDate date = record.getDate();
@@ -268,9 +165,35 @@ public class InventoryService {
         return result;
     }
 
-    private List<Map<String, Object>> buildSkuSummaryTable(Map<String, double[]> purchaseSummary,
-                                                           Map<String, Double> salesSummary,
-                                                           Map<String, ProductInfo> productInfoMap) {
+    public List<Map<String, Object>> getSkuSummaryTable() {
+        List<PurchaseRecord> purchaseRecords = dataLoader.getPurchaseRecords();
+        List<SalesRecord> salesRecords = dataLoader.getSalesRecords();
+        Map<String, ProductInfo> productInfoMap = dataLoader.getProductInfo().stream()
+                .filter(info -> info.getProductCode() != null && !info.getProductCode().trim().isEmpty())
+                .collect(Collectors.toMap(ProductInfo::getProductCode, info -> info, (a, b) -> a));
+        Set<String> finishedSkus = getFinishedSkus();
+
+        if (!finishedSkus.isEmpty()) {
+            purchaseRecords = purchaseRecords.stream()
+                    .filter(r -> finishedSkus.contains(r.getProductCode()))
+                    .collect(Collectors.toList());
+            salesRecords = salesRecords.stream()
+                    .filter(r -> finishedSkus.contains(r.getProductCode()))
+                    .collect(Collectors.toList());
+        }
+
+        Map<String, double[]> purchaseSummary = new HashMap<>();
+        for (PurchaseRecord record : purchaseRecords) {
+            double[] agg = purchaseSummary.computeIfAbsent(record.getProductCode(), k -> new double[2]);
+            agg[0] += record.getQuantity();
+            agg[1] += record.getAmount();
+        }
+
+        Map<String, Double> salesSummary = new HashMap<>();
+        for (SalesRecord record : salesRecords) {
+            salesSummary.merge(record.getProductCode(), record.getQuantity(), Double::sum);
+        }
+
         double totalAmount = purchaseSummary.values().stream().mapToDouble(v -> v[1]).sum();
 
         List<Map<String, Object>> result = new ArrayList<>();
@@ -301,14 +224,40 @@ 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.subList(0, 20);
         }
         return result;
     }
 
-    private List<Map<String, Object>> buildSpuSummaryTable(Map<String, double[]> purchaseSummary,
-                                                           Map<String, Double> salesSummary,
-                                                           Map<String, ProductInfo> productInfoMap) {
+    public List<Map<String, Object>> getSpuSummaryTable() {
+        List<PurchaseRecord> purchaseRecords = dataLoader.getPurchaseRecords();
+        List<SalesRecord> salesRecords = dataLoader.getSalesRecords();
+        Map<String, ProductInfo> productInfoMap = dataLoader.getProductInfo().stream()
+                .filter(info -> info.getProductCode() != null && !info.getProductCode().trim().isEmpty())
+                .collect(Collectors.toMap(ProductInfo::getProductCode, info -> info, (a, b) -> a));
+        Set<String> finishedSkus = getFinishedSkus();
+
+        if (!finishedSkus.isEmpty()) {
+            purchaseRecords = purchaseRecords.stream()
+                    .filter(r -> finishedSkus.contains(r.getProductCode()))
+                    .collect(Collectors.toList());
+            salesRecords = salesRecords.stream()
+                    .filter(r -> finishedSkus.contains(r.getProductCode()))
+                    .collect(Collectors.toList());
+        }
+
+        Map<String, double[]> purchaseSummary = new HashMap<>();
+        for (PurchaseRecord record : purchaseRecords) {
+            double[] agg = purchaseSummary.computeIfAbsent(record.getProductCode(), k -> new double[2]);
+            agg[0] += record.getQuantity();
+            agg[1] += record.getAmount();
+        }
+
+        Map<String, Double> salesSummary = new HashMap<>();
+        for (SalesRecord record : salesRecords) {
+            salesSummary.merge(record.getProductCode(), record.getQuantity(), Double::sum);
+        }
+
         Map<String, SpuAggregate> aggregates = new HashMap<>();
         for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
             String sku = entry.getKey();
@@ -354,6 +303,24 @@ public class InventoryService {
         return result;
     }
 
+    public List<Map<String, Object>> getMonthlyTurnoverTable() {
+        return Collections.emptyList();
+    }
+
+    public Map<String, Object> getSettings() {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("data", StorageSettings.getAnalysisWeights());
+        result.put("defaults", StorageSettings.DEFAULT_ANALYSIS_WEIGHTS);
+        return result;
+    }
+
+    public Map<String, Object> updateSettings(Map<String, Object> payload) {
+        StorageSettings.updateAnalysisWeights(payload);
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("data", StorageSettings.getAnalysisWeights());
+        return result;
+    }
+
     private int calculateAssemblyQuantity(Set<String> finishedSkus) {
         List<AssemblyRecord> assemblyRecords = dataLoader.getAssemblyRecords();
         if (assemblyRecords.isEmpty()) {
@@ -370,14 +337,19 @@ public class InventoryService {
                 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() && finished.stream().anyMatch(finishedSkus::contains)) {
-                    total += record.getQuantity();
+                if (finished != null && !finished.isEmpty()) {
+                    boolean match = finished.stream().anyMatch(finishedSkus::contains);
+                    if (match) {
+                        total += record.getQuantity();
+                    }
                 }
             }
         }
@@ -417,7 +389,7 @@ public class InventoryService {
         if (raw == null || raw.trim().isEmpty()) {
             return;
         }
-        String normalized = raw.replace(',', ',').replace('、', ',').replace(';', ',').replace(';', ',');
+        String normalized = raw.replace(',', ',');
         for (String part : normalized.split(",")) {
             String code = part.trim();
             if (!code.isEmpty()) {
@@ -427,10 +399,7 @@ public class InventoryService {
     }
 
     private Set<String> getFinishedSkus() {
-        return getFinishedSkus(dataLoader.getProductInfo());
-    }
-
-    private Set<String> getFinishedSkus(List<ProductInfo> infos) {
+        List<ProductInfo> infos = dataLoader.getProductInfo();
         if (infos.isEmpty()) {
             return Collections.emptySet();
         }
@@ -512,21 +481,6 @@ public class InventoryService {
                     .orElse("");
         }
     }
-
-    private static class InventorySnapshot {
-        private final Map<String, Object> overviewData;
-        private final Map<String, Object> monthlyComparisonData;
-        private final List<Map<String, Object>> skuSummaryTable;
-        private final List<Map<String, Object>> spuSummaryTable;
-
-        private InventorySnapshot(Map<String, Object> overviewData,
-                                  Map<String, Object> monthlyComparisonData,
-                                  List<Map<String, Object>> skuSummaryTable,
-                                  List<Map<String, Object>> spuSummaryTable) {
-            this.overviewData = overviewData;
-            this.monthlyComparisonData = monthlyComparisonData;
-            this.skuSummaryTable = skuSummaryTable;
-            this.spuSummaryTable = spuSummaryTable;
-        }
-    }
 }
+
+

+ 21 - 81
dtm-system/src/main/java/com/dtm/storage/service/StorageUploadService.java

@@ -16,25 +16,19 @@ public class StorageUploadService {
     private static final String[] ALLOWED_EXT = new String[]{".xlsx", ".xls"};
 
     private final StorageDataLoader dataLoader;
-    private final InventoryService inventoryService;
-    private final RiskService riskService;
     private Path activeTempDir;
 
-    public StorageUploadService(StorageDataLoader dataLoader,
-                                InventoryService inventoryService,
-                                RiskService riskService) {
+    public StorageUploadService(StorageDataLoader dataLoader) {
         this.dataLoader = dataLoader;
-        this.inventoryService = inventoryService;
-        this.riskService = riskService;
     }
 
-    public UploadBatch saveUploadFiles(MultipartFile purchaseFile,
-                                       MultipartFile salesFile,
-                                       MultipartFile assemblyFile,
-                                       MultipartFile productFile,
-                                       MultipartFile semiMappingFile) {
+    public Map<String, Object> uploadFiles(MultipartFile purchaseFile,
+                                           MultipartFile salesFile,
+                                           MultipartFile assemblyFile,
+                                           MultipartFile productFile,
+                                           MultipartFile semiMappingFile) {
         List<Map<String, Object>> saved = new ArrayList<>();
-        Path basePath = createTempDir();
+        Path basePath = prepareTempDir();
 
         int savedCount = 0;
         if (purchaseFile != null && !purchaseFile.isEmpty()) {
@@ -62,56 +56,32 @@ public class StorageUploadService {
             throw new IllegalArgumentException("请至少选择一个要上传的文件");
         }
 
-        return new UploadBatch(basePath, saved, savedCount);
-    }
-
-    public synchronized void activateUploadBatch(UploadBatch batch) {
-        if (batch == null || batch.getBasePath() == null) {
-            throw new IllegalArgumentException("上传批次无效");
-        }
-        Path previous = activeTempDir;
-        dataLoader.useTemporaryBasePath(batch.getBasePath());
-        inventoryService.invalidateCache();
-        riskService.invalidateCache();
-        inventoryService.warmCache();
-        riskService.getRiskStatistics();
-        activeTempDir = batch.getBasePath();
-        if (previous != null && !previous.equals(activeTempDir)) {
-            cleanupDir(previous);
-        }
-    }
+        dataLoader.useTemporaryBasePath(basePath);
 
-    public void discardUploadBatch(UploadBatch batch) {
-        if (batch == null) {
-            return;
-        }
-        Path path = batch.getBasePath();
-        if (path == null) {
-            return;
-        }
-        synchronized (this) {
-            if (path.equals(activeTempDir)) {
-                return;
-            }
-        }
-        cleanupDir(path);
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("basePath", basePath.toAbsolutePath().toString());
+        result.put("files", saved);
+        result.put("count", savedCount);
+        return result;
     }
 
-    private Path createTempDir() {
+    private synchronized Path prepareTempDir() {
+        cleanupTempDir();
         try {
             Path temp = Files.createTempDirectory("dtm-storage-");
+            this.activeTempDir = temp;
             return temp;
         } catch (Exception e) {
             throw new IllegalStateException("创建临时目录失败: " + e.getMessage(), e);
         }
     }
 
-    private void cleanupDir(Path targetDir) {
-        if (targetDir == null || !Files.exists(targetDir)) {
+    private void cleanupTempDir() {
+        if (activeTempDir == null || !Files.exists(activeTempDir)) {
             return;
         }
         try {
-            Files.walk(targetDir)
+            Files.walk(activeTempDir)
                     .sorted((a, b) -> b.compareTo(a))
                     .forEach(path -> {
                         try {
@@ -120,6 +90,8 @@ public class StorageUploadService {
                         }
                     });
         } catch (Exception ignored) {
+        } finally {
+            activeTempDir = null;
         }
     }
 
@@ -177,36 +149,4 @@ public class StorageUploadService {
         }
         return false;
     }
-
-    public static class UploadBatch {
-        private final Path basePath;
-        private final List<Map<String, Object>> files;
-        private final int count;
-
-        public UploadBatch(Path basePath, List<Map<String, Object>> files, int count) {
-            this.basePath = basePath;
-            this.files = files;
-            this.count = count;
-        }
-
-        public Path getBasePath() {
-            return basePath;
-        }
-
-        public List<Map<String, Object>> getFiles() {
-            return files;
-        }
-
-        public int getCount() {
-            return count;
-        }
-
-        public Map<String, Object> toResult() {
-            Map<String, Object> result = new LinkedHashMap<>();
-            result.put("basePath", basePath == null ? null : basePath.toAbsolutePath().toString());
-            result.put("files", files);
-            result.put("count", count);
-            return result;
-        }
-    }
 }