|
|
@@ -1,4 +1,4 @@
|
|
|
-package com.dtm.storage.service;
|
|
|
+package com.dtm.storage.service;
|
|
|
|
|
|
import com.dtm.storage.config.StorageSettings;
|
|
|
import com.dtm.storage.model.AssemblyRecord;
|
|
|
@@ -25,46 +25,15 @@ import java.util.stream.Collectors;
|
|
|
@Service
|
|
|
public class InventoryService {
|
|
|
private final StorageDataLoader dataLoader;
|
|
|
+ private final Object snapshotLock = new Object();
|
|
|
+ private volatile InventorySnapshot snapshot;
|
|
|
|
|
|
public InventoryService(StorageDataLoader dataLoader) {
|
|
|
this.dataLoader = dataLoader;
|
|
|
}
|
|
|
|
|
|
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());
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
+ return ensureSnapshot().overview;
|
|
|
}
|
|
|
|
|
|
public Map<String, Object> getHealthIndex() {
|
|
|
@@ -111,59 +80,139 @@ public class InventoryService {
|
|
|
}
|
|
|
|
|
|
public Map<String, Object> getMonthlyComparisonData() {
|
|
|
- List<PurchaseRecord> purchaseRecords = dataLoader.getPurchaseRecords();
|
|
|
- List<SalesRecord> salesRecords = dataLoader.getSalesRecords();
|
|
|
- List<AssemblyRecord> assemblyRecords = dataLoader.getAssemblyRecords();
|
|
|
- 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());
|
|
|
+ return ensureSnapshot().monthlyComparison;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<Map<String, Object>> getSkuSummaryTable() {
|
|
|
+ return ensureSnapshot().skuSummary;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<Map<String, Object>> getSpuSummaryTable() {
|
|
|
+ return ensureSnapshot().spuSummary;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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());
|
|
|
+ invalidateCache();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void invalidateCache() {
|
|
|
+ snapshot = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void warmCache() {
|
|
|
+ ensureSnapshot();
|
|
|
+ }
|
|
|
+
|
|
|
+ private InventorySnapshot ensureSnapshot() {
|
|
|
+ InventorySnapshot current = snapshot;
|
|
|
+ if (current != null) {
|
|
|
+ return current;
|
|
|
}
|
|
|
+ synchronized (snapshotLock) {
|
|
|
+ current = snapshot;
|
|
|
+ if (current == null) {
|
|
|
+ current = buildSnapshot();
|
|
|
+ snapshot = current;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return current;
|
|
|
+ }
|
|
|
|
|
|
- Map<String, Set<String>> semiToFinished = buildSemiToFinishedMap();
|
|
|
+ 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);
|
|
|
|
|
|
+ Map<String, double[]> purchaseSummary = new HashMap<>();
|
|
|
+ Map<String, Double> salesSummary = new HashMap<>();
|
|
|
Map<YearMonth, Double> purchaseMonthly = new TreeMap<>();
|
|
|
+ Map<YearMonth, Double> salesMonthly = new TreeMap<>();
|
|
|
+
|
|
|
+ double totalPurchaseQtyRaw = 0.0;
|
|
|
+ double totalSalesQtyRaw = 0.0;
|
|
|
+ double totalPurchaseAmountRaw = 0.0;
|
|
|
+
|
|
|
for (PurchaseRecord record : purchaseRecords) {
|
|
|
- LocalDate date = record.getDate();
|
|
|
- if (date == null) {
|
|
|
+ String code = record.getProductCode();
|
|
|
+ if (!shouldIncludeSku(code, finishedSkus)) {
|
|
|
continue;
|
|
|
}
|
|
|
- YearMonth month = YearMonth.from(date);
|
|
|
- purchaseMonthly.merge(month, record.getQuantity(), Double::sum);
|
|
|
- }
|
|
|
+ totalPurchaseQtyRaw += record.getQuantity();
|
|
|
+ totalPurchaseAmountRaw += record.getAmount();
|
|
|
+ double[] agg = purchaseSummary.computeIfAbsent(code, key -> new double[2]);
|
|
|
+ agg[0] += record.getQuantity();
|
|
|
+ agg[1] += record.getAmount();
|
|
|
|
|
|
- Map<YearMonth, Double> salesMonthly = new TreeMap<>();
|
|
|
- for (SalesRecord record : salesRecords) {
|
|
|
LocalDate date = record.getDate();
|
|
|
- if (date == null) {
|
|
|
- continue;
|
|
|
+ if (date != null) {
|
|
|
+ purchaseMonthly.merge(YearMonth.from(date), record.getQuantity(), Double::sum);
|
|
|
}
|
|
|
- YearMonth month = YearMonth.from(date);
|
|
|
- salesMonthly.merge(month, record.getQuantity(), Double::sum);
|
|
|
}
|
|
|
|
|
|
- Map<YearMonth, Double> assemblyMonthly = new TreeMap<>();
|
|
|
- for (AssemblyRecord record : assemblyRecords) {
|
|
|
- LocalDate date = record.getDate();
|
|
|
- if (date == null) {
|
|
|
+ for (SalesRecord record : salesRecords) {
|
|
|
+ String code = record.getProductCode();
|
|
|
+ if (!shouldIncludeSku(code, finishedSkus)) {
|
|
|
continue;
|
|
|
}
|
|
|
- if (!isAssemblyForFinished(record, finishedSkus, semiToFinished)) {
|
|
|
- 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);
|
|
|
}
|
|
|
- YearMonth month = YearMonth.from(date);
|
|
|
- assemblyMonthly.merge(month, record.getQuantity(), Double::sum);
|
|
|
}
|
|
|
|
|
|
+ int totalPurchaseQty = (int) Math.round(totalPurchaseQtyRaw);
|
|
|
+ int totalSalesQty = (int) Math.round(totalSalesQtyRaw);
|
|
|
+ int assemblyQty = calculateAssemblyQuantity(finishedSkus);
|
|
|
+ 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;
|
|
|
+ double turnoverRate = avgInventory > 0 ? (totalSalesQty / avgInventory) : 0.0;
|
|
|
+ turnoverRate = Math.round(turnoverRate * 100.0) / 100.0;
|
|
|
+
|
|
|
+ Map<String, Object> overview = new LinkedHashMap<>();
|
|
|
+ overview.put("totalInventory", totalInventory);
|
|
|
+ overview.put("totalValue", totalValue);
|
|
|
+ overview.put("turnoverRate", turnoverRate);
|
|
|
+ overview.put("inTransitRatio", 12.5);
|
|
|
+ overview.put("purchaseQty", totalPurchaseQty);
|
|
|
+ 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);
|
|
|
+
|
|
|
+ dataLoader.clearCache();
|
|
|
+ return new InventorySnapshot(overview, monthlyComparison, skuSummary, spuSummary);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildMonthlyComparison(Map<YearMonth, Double> purchaseMonthly,
|
|
|
+ Map<YearMonth, Double> salesMonthly) {
|
|
|
Set<YearMonth> allMonths = new HashSet<>();
|
|
|
allMonths.addAll(purchaseMonthly.keySet());
|
|
|
allMonths.addAll(salesMonthly.keySet());
|
|
|
- allMonths.addAll(assemblyMonthly.keySet());
|
|
|
List<YearMonth> sorted = new ArrayList<>(allMonths);
|
|
|
Collections.sort(sorted);
|
|
|
|
|
|
@@ -171,26 +220,17 @@ public class InventoryService {
|
|
|
List<Integer> purchase = new ArrayList<>();
|
|
|
List<Integer> sales = new ArrayList<>();
|
|
|
List<Integer> inventory = new ArrayList<>();
|
|
|
- double cumulativeInventory = 0.0;
|
|
|
+ int cumulativeInventory = 0;
|
|
|
|
|
|
for (YearMonth month : sorted) {
|
|
|
double purchaseQty = purchaseMonthly.getOrDefault(month, 0.0);
|
|
|
double salesQty = salesMonthly.getOrDefault(month, 0.0);
|
|
|
- double assemblyQty = assemblyMonthly.getOrDefault(month, 0.0);
|
|
|
- cumulativeInventory += (purchaseQty + assemblyQty - salesQty);
|
|
|
+ cumulativeInventory += (int) Math.round(purchaseQty - salesQty);
|
|
|
|
|
|
months.add(month.toString());
|
|
|
purchase.add((int) Math.round(purchaseQty));
|
|
|
sales.add((int) Math.round(salesQty));
|
|
|
- inventory.add((int) Math.round(cumulativeInventory));
|
|
|
- }
|
|
|
-
|
|
|
- if (!inventory.isEmpty()) {
|
|
|
- int totalPurchaseQty = (int) Math.round(purchaseRecords.stream().mapToDouble(PurchaseRecord::getQuantity).sum());
|
|
|
- int totalSalesQty = (int) Math.round(salesRecords.stream().mapToDouble(SalesRecord::getQuantity).sum());
|
|
|
- int totalAssemblyQty = calculateAssemblyQuantity(finishedSkus);
|
|
|
- int totalInventory = totalPurchaseQty + totalAssemblyQty - totalSalesQty;
|
|
|
- inventory.set(inventory.size() - 1, totalInventory);
|
|
|
+ inventory.add(cumulativeInventory);
|
|
|
}
|
|
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
|
@@ -201,37 +241,10 @@ public class InventoryService {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
+ private List<Map<String, Object>> buildSkuSummary(Map<String, double[]> purchaseSummary,
|
|
|
+ Map<String, Double> salesSummary,
|
|
|
+ Map<String, ProductInfo> productInfoMap) {
|
|
|
double totalAmount = purchaseSummary.values().stream().mapToDouble(v -> v[1]).sum();
|
|
|
-
|
|
|
List<Map<String, Object>> result = new ArrayList<>();
|
|
|
for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
|
|
|
String sku = entry.getKey();
|
|
|
@@ -260,48 +273,22 @@ public class InventoryService {
|
|
|
|
|
|
result.sort(Comparator.comparing((Map<String, Object> row) -> ((Number) row.getOrDefault("purchaseQty", 0)).doubleValue()).reversed());
|
|
|
if (result.size() > 20) {
|
|
|
- return result.subList(0, 20);
|
|
|
+ return new ArrayList<>(result.subList(0, 20));
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
+ private List<Map<String, Object>> buildSpuSummary(Map<String, double[]> purchaseSummary,
|
|
|
+ Map<String, Double> salesSummary,
|
|
|
+ Map<String, ProductInfo> productInfoMap) {
|
|
|
Map<String, SpuAggregate> aggregates = new HashMap<>();
|
|
|
for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
|
|
|
String sku = entry.getKey();
|
|
|
ProductInfo info = productInfoMap.get(sku);
|
|
|
String spu = info != null && info.getSpuName() != null && !info.getSpuName().trim().isEmpty()
|
|
|
? info.getSpuName()
|
|
|
- : "未命名SPU";
|
|
|
- SpuAggregate agg = aggregates.computeIfAbsent(spu, k -> new SpuAggregate());
|
|
|
+ : "鏈懡鍚峉PU";
|
|
|
+ SpuAggregate agg = aggregates.computeIfAbsent(spu, key -> new SpuAggregate());
|
|
|
agg.purchaseQty += entry.getValue()[0];
|
|
|
agg.purchaseAmount += entry.getValue()[1];
|
|
|
agg.salesQty += salesSummary.getOrDefault(sku, 0.0);
|
|
|
@@ -339,70 +326,39 @@ 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()) {
|
|
|
return 0;
|
|
|
}
|
|
|
Map<String, Set<String>> semiToFinished = buildSemiToFinishedMap();
|
|
|
- if (finishedSkus == null || finishedSkus.isEmpty()) {
|
|
|
+ if (semiToFinished.isEmpty() || finishedSkus == null || finishedSkus.isEmpty()) {
|
|
|
return (int) Math.round(assemblyRecords.stream().mapToDouble(AssemblyRecord::getQuantity).sum());
|
|
|
}
|
|
|
double total = 0.0;
|
|
|
for (AssemblyRecord record : assemblyRecords) {
|
|
|
- if (!isAssemblyForFinished(record, finishedSkus, semiToFinished)) {
|
|
|
+ String code = record.getProductCode();
|
|
|
+ if (code == null || code.trim().isEmpty()) {
|
|
|
continue;
|
|
|
}
|
|
|
- total += record.getQuantity();
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
return (int) Math.round(total);
|
|
|
}
|
|
|
|
|
|
- private boolean isAssemblyForFinished(AssemblyRecord record,
|
|
|
- Set<String> finishedSkus,
|
|
|
- Map<String, Set<String>> semiToFinished) {
|
|
|
- if (record == null) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (finishedSkus == null || finishedSkus.isEmpty()) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- String code = record.getProductCode();
|
|
|
- if (code == null || code.trim().isEmpty()) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (finishedSkus.contains(code)) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- if (semiToFinished == null || semiToFinished.isEmpty()) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- Set<String> finished = semiToFinished.get(code);
|
|
|
- if (finished == null || finished.isEmpty()) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- return finished.stream().anyMatch(finishedSkus::contains);
|
|
|
- }
|
|
|
-
|
|
|
private Map<String, Set<String>> buildSemiToFinishedMap() {
|
|
|
List<List<Object>> rows = dataLoader.getSemiMappingRows();
|
|
|
List<String> headers = dataLoader.getSemiMappingHeaders();
|
|
|
@@ -410,10 +366,10 @@ public class InventoryService {
|
|
|
return Collections.emptyMap();
|
|
|
}
|
|
|
|
|
|
- int prodIdx = findHeaderIndex(headers, new String[]{"成品", "成品编码", "产品编码", "产品代码"}, 0);
|
|
|
- int innerIdx = findHeaderIndex(headers, new String[]{"内芯", "内胆"}, 1);
|
|
|
- int outerIdx = findHeaderIndex(headers, new String[]{"外壳", "外套", "外壳编码"}, 2);
|
|
|
- int accessoryIdx = findHeaderIndex(headers, new String[]{"配件", "附件"}, 3);
|
|
|
+ int prodIdx = findHeaderIndex(headers, new String[]{"鎴愬搧", "鎴愬搧缂栫爜", "浜у搧缂栫爜", "浜у搧浠g爜"}, 0);
|
|
|
+ int innerIdx = findHeaderIndex(headers, new String[]{"鍐呰姱", "鍐呰儐"}, 1);
|
|
|
+ int outerIdx = findHeaderIndex(headers, new String[]{"澶栧3", "澶栧", "澶栧3缂栫爜"}, 2);
|
|
|
+ int accessoryIdx = findHeaderIndex(headers, new String[]{"閰嶄欢", "闄勪欢"}, 3);
|
|
|
|
|
|
Map<String, Set<String>> map = new HashMap<>();
|
|
|
for (List<Object> row : rows) {
|
|
|
@@ -426,7 +382,7 @@ public class InventoryService {
|
|
|
collectCodes(semiCodes, toText(getValue(row, outerIdx)));
|
|
|
collectCodes(semiCodes, toText(getValue(row, accessoryIdx)));
|
|
|
for (String code : semiCodes) {
|
|
|
- map.computeIfAbsent(code, k -> new HashSet<>()).add(productCode);
|
|
|
+ map.computeIfAbsent(code, key -> new HashSet<>()).add(productCode);
|
|
|
}
|
|
|
}
|
|
|
return map;
|
|
|
@@ -436,7 +392,7 @@ public class InventoryService {
|
|
|
if (raw == null || raw.trim().isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
- String normalized = raw.replace(',', ',');
|
|
|
+ String normalized = raw.replace(',', ',').replace("锛?", ",");
|
|
|
for (String part : normalized.split(",")) {
|
|
|
String code = part.trim();
|
|
|
if (!code.isEmpty()) {
|
|
|
@@ -446,7 +402,10 @@ public class InventoryService {
|
|
|
}
|
|
|
|
|
|
private Set<String> getFinishedSkus() {
|
|
|
- List<ProductInfo> infos = dataLoader.getProductInfo();
|
|
|
+ return getFinishedSkus(dataLoader.getProductInfo());
|
|
|
+ }
|
|
|
+
|
|
|
+ private Set<String> getFinishedSkus(List<ProductInfo> infos) {
|
|
|
if (infos.isEmpty()) {
|
|
|
return Collections.emptySet();
|
|
|
}
|
|
|
@@ -462,7 +421,7 @@ public class InventoryService {
|
|
|
continue;
|
|
|
}
|
|
|
String text = category.toLowerCase(Locale.ROOT);
|
|
|
- if (text.contains("半成") || text.contains("辅料") || text.contains("配件")) {
|
|
|
+ if (text.contains("鍗婃垚") || text.contains("杈呮枡") || text.contains("閰嶄欢")) {
|
|
|
continue;
|
|
|
}
|
|
|
finished.add(code);
|
|
|
@@ -470,6 +429,13 @@ public class InventoryService {
|
|
|
return finished;
|
|
|
}
|
|
|
|
|
|
+ private boolean shouldIncludeSku(String code, Set<String> finishedSkus) {
|
|
|
+ if (code == null || code.trim().isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return finishedSkus.isEmpty() || finishedSkus.contains(code);
|
|
|
+ }
|
|
|
+
|
|
|
private Map<String, Object> buildFsm(String name, int count, String trend, double percentage) {
|
|
|
Map<String, Object> row = new LinkedHashMap<>();
|
|
|
row.put("name", name);
|
|
|
@@ -528,6 +494,21 @@ public class InventoryService {
|
|
|
.orElse("");
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
|
|
|
+ private static class InventorySnapshot {
|
|
|
+ private final Map<String, Object> overview;
|
|
|
+ private final Map<String, Object> monthlyComparison;
|
|
|
+ private final List<Map<String, Object>> skuSummary;
|
|
|
+ private final List<Map<String, Object>> spuSummary;
|
|
|
+
|
|
|
+ private InventorySnapshot(Map<String, Object> overview,
|
|
|
+ Map<String, Object> monthlyComparison,
|
|
|
+ List<Map<String, Object>> skuSummary,
|
|
|
+ List<Map<String, Object>> spuSummary) {
|
|
|
+ this.overview = overview;
|
|
|
+ this.monthlyComparison = monthlyComparison;
|
|
|
+ this.skuSummary = skuSummary;
|
|
|
+ this.spuSummary = spuSummary;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|