|
|
@@ -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,79 +25,48 @@ 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() {
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
|
result.put("value", 78);
|
|
|
- result.put("status", "一般");
|
|
|
+ result.put("status", "涓€鑸?");
|
|
|
result.put("trend", "up");
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public List<Map<String, Object>> getFsmStates() {
|
|
|
List<Map<String, Object>> list = new ArrayList<>();
|
|
|
- list.add(buildFsm("正常库存", 3250, "up", 5.2));
|
|
|
- list.add(buildFsm("低库存", 820, "down", 2.1));
|
|
|
- list.add(buildFsm("超储", 450, "up", 3.5));
|
|
|
- list.add(buildFsm("滞销", 230, "down", 1.2));
|
|
|
- list.add(buildFsm("在途", 580, "up", 4.8));
|
|
|
- list.add(buildFsm("待检", 350, "down", 0.5));
|
|
|
+ list.add(buildFsm("姝e父搴撳瓨", 3250, "up", 5.2));
|
|
|
+ list.add(buildFsm("浣庡簱瀛?", 820, "down", 2.1));
|
|
|
+ list.add(buildFsm("瓒呭偍", 450, "up", 3.5));
|
|
|
+ list.add(buildFsm("婊為攢", 230, "down", 1.2));
|
|
|
+ list.add(buildFsm("鍦ㄩ€?", 580, "up", 4.8));
|
|
|
+ list.add(buildFsm("寰呮", 350, "down", 0.5));
|
|
|
return list;
|
|
|
}
|
|
|
|
|
|
public Map<String, Object> getLifecycleDistribution() {
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
|
Map<String, Integer> finished = new LinkedHashMap<>();
|
|
|
- finished.put("引入期", 3200);
|
|
|
- finished.put("成长期", 8500);
|
|
|
- finished.put("成熟期", 12800);
|
|
|
- finished.put("衰退期", 4060);
|
|
|
+ finished.put("寮曞叆鏈?", 3200);
|
|
|
+ finished.put("鎴愰暱鏈?", 8500);
|
|
|
+ finished.put("鎴愮啛鏈?", 12800);
|
|
|
+ finished.put("琛伴€€鏈?", 4060);
|
|
|
Map<String, Integer> semi = new LinkedHashMap<>();
|
|
|
- semi.put("引入期", 2100);
|
|
|
- semi.put("成长期", 5800);
|
|
|
- semi.put("成熟期", 7200);
|
|
|
- semi.put("衰退期", 2020);
|
|
|
+ semi.put("寮曞叆鏈?", 2100);
|
|
|
+ semi.put("鎴愰暱鏈?", 5800);
|
|
|
+ semi.put("鎴愮啛鏈?", 7200);
|
|
|
+ semi.put("琛伴€€鏈?", 2020);
|
|
|
result.put("finished", finished);
|
|
|
result.put("semi_finished", semi);
|
|
|
return result;
|
|
|
@@ -111,29 +80,136 @@ public class InventoryService {
|
|
|
}
|
|
|
|
|
|
public Map<String, Object> getMonthlyComparisonData() {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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();
|
|
|
+
|
|
|
+ LocalDate date = record.getDate();
|
|
|
+ if (date != null) {
|
|
|
+ purchaseMonthly.merge(YearMonth.from(date), record.getQuantity(), Double::sum);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- Map<YearMonth, Double> salesMonthly = new TreeMap<>();
|
|
|
for (SalesRecord record : salesRecords) {
|
|
|
- LocalDate date = record.getDate();
|
|
|
- if (date == null) {
|
|
|
+ String code = record.getProductCode();
|
|
|
+ if (!shouldIncludeSku(code, finishedSkus)) {
|
|
|
continue;
|
|
|
}
|
|
|
- YearMonth month = YearMonth.from(date);
|
|
|
- salesMonthly.merge(month, record.getQuantity(), Double::sum);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ 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());
|
|
|
@@ -165,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();
|
|
|
@@ -224,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);
|
|
|
@@ -303,24 +326,6 @@ 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()) {
|
|
|
@@ -337,12 +342,10 @@ 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()) {
|
|
|
@@ -363,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) {
|
|
|
@@ -379,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;
|
|
|
@@ -389,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()) {
|
|
|
@@ -399,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();
|
|
|
}
|
|
|
@@ -415,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);
|
|
|
@@ -423,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);
|
|
|
@@ -481,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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|