|
@@ -143,7 +143,9 @@ public class InventoryService {
|
|
|
Set<String> finishedSkus = getFinishedSkus(productInfos);
|
|
Set<String> finishedSkus = getFinishedSkus(productInfos);
|
|
|
|
|
|
|
|
Map<String, double[]> purchaseSummary = new HashMap<>();
|
|
Map<String, double[]> purchaseSummary = new HashMap<>();
|
|
|
|
|
+ Map<String, double[]> finishedPurchaseSummary = new HashMap<>();
|
|
|
Map<String, Double> salesSummary = new HashMap<>();
|
|
Map<String, Double> salesSummary = new HashMap<>();
|
|
|
|
|
+ Map<String, Double> finishedSalesSummary = new HashMap<>();
|
|
|
Map<YearMonth, Double> purchaseMonthly = new TreeMap<>();
|
|
Map<YearMonth, Double> purchaseMonthly = new TreeMap<>();
|
|
|
Map<YearMonth, Double> salesMonthly = new TreeMap<>();
|
|
Map<YearMonth, Double> salesMonthly = new TreeMap<>();
|
|
|
|
|
|
|
@@ -153,38 +155,46 @@ public class InventoryService {
|
|
|
|
|
|
|
|
for (PurchaseRecord record : purchaseRecords) {
|
|
for (PurchaseRecord record : purchaseRecords) {
|
|
|
String code = record.getProductCode();
|
|
String code = record.getProductCode();
|
|
|
- if (!shouldIncludeSku(code, finishedSkus)) {
|
|
|
|
|
|
|
+ if (code == null || code.trim().isEmpty()) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- totalPurchaseQtyRaw += record.getQuantity();
|
|
|
|
|
- totalPurchaseAmountRaw += record.getAmount();
|
|
|
|
|
double[] agg = purchaseSummary.computeIfAbsent(code, key -> new double[2]);
|
|
double[] agg = purchaseSummary.computeIfAbsent(code, key -> new double[2]);
|
|
|
agg[0] += record.getQuantity();
|
|
agg[0] += record.getQuantity();
|
|
|
agg[1] += record.getAmount();
|
|
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) {
|
|
for (SalesRecord record : salesRecords) {
|
|
|
String code = record.getProductCode();
|
|
String code = record.getProductCode();
|
|
|
- if (!shouldIncludeSku(code, finishedSkus)) {
|
|
|
|
|
|
|
+ if (code == null || code.trim().isEmpty()) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- totalSalesQtyRaw += record.getQuantity();
|
|
|
|
|
salesSummary.merge(code, record.getQuantity(), Double::sum);
|
|
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 totalPurchaseQty = (int) Math.round(totalPurchaseQtyRaw);
|
|
|
int totalSalesQty = (int) Math.round(totalSalesQtyRaw);
|
|
int totalSalesQty = (int) Math.round(totalSalesQtyRaw);
|
|
|
- int assemblyQty = calculateAssemblyQuantity(finishedSkus);
|
|
|
|
|
|
|
+ AssemblyAggregation assemblyAggregation = buildAssemblyAggregation(finishedSkus);
|
|
|
|
|
+ int assemblyQty = assemblyAggregation.totalQty;
|
|
|
int totalInventory = totalPurchaseQty + assemblyQty - totalSalesQty;
|
|
int totalInventory = totalPurchaseQty + assemblyQty - totalSalesQty;
|
|
|
double totalValue = Math.round((totalPurchaseAmountRaw / 10000.0) * 100.0) / 100.0;
|
|
double totalValue = Math.round((totalPurchaseAmountRaw / 10000.0) * 100.0) / 100.0;
|
|
|
double avgInventory = totalInventory > 0 ? totalInventory / 2.0 : 1.0;
|
|
double avgInventory = totalInventory > 0 ? totalInventory / 2.0 : 1.0;
|
|
@@ -200,19 +210,34 @@ public class InventoryService {
|
|
|
overview.put("salesQty", totalSalesQty);
|
|
overview.put("salesQty", totalSalesQty);
|
|
|
overview.put("assemblyQty", assemblyQty);
|
|
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();
|
|
dataLoader.clearCache();
|
|
|
return new InventorySnapshot(overview, monthlyComparison, skuSummary, spuSummary);
|
|
return new InventorySnapshot(overview, monthlyComparison, skuSummary, spuSummary);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private Map<String, Object> buildMonthlyComparison(Map<YearMonth, Double> purchaseMonthly,
|
|
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<>();
|
|
Set<YearMonth> allMonths = new HashSet<>();
|
|
|
allMonths.addAll(purchaseMonthly.keySet());
|
|
allMonths.addAll(purchaseMonthly.keySet());
|
|
|
allMonths.addAll(salesMonthly.keySet());
|
|
allMonths.addAll(salesMonthly.keySet());
|
|
|
|
|
+ if (assemblyMonthly != null) {
|
|
|
|
|
+ allMonths.addAll(assemblyMonthly.keySet());
|
|
|
|
|
+ }
|
|
|
List<YearMonth> sorted = new ArrayList<>(allMonths);
|
|
List<YearMonth> sorted = new ArrayList<>(allMonths);
|
|
|
Collections.sort(sorted);
|
|
Collections.sort(sorted);
|
|
|
|
|
|
|
@@ -225,7 +250,8 @@ public class InventoryService {
|
|
|
for (YearMonth month : sorted) {
|
|
for (YearMonth month : sorted) {
|
|
|
double purchaseQty = purchaseMonthly.getOrDefault(month, 0.0);
|
|
double purchaseQty = purchaseMonthly.getOrDefault(month, 0.0);
|
|
|
double salesQty = salesMonthly.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());
|
|
months.add(month.toString());
|
|
|
purchase.add((int) Math.round(purchaseQty));
|
|
purchase.add((int) Math.round(purchaseQty));
|
|
@@ -243,16 +269,26 @@ public class InventoryService {
|
|
|
|
|
|
|
|
private List<Map<String, Object>> buildSkuSummary(Map<String, double[]> purchaseSummary,
|
|
private List<Map<String, Object>> buildSkuSummary(Map<String, double[]> purchaseSummary,
|
|
|
Map<String, Double> salesSummary,
|
|
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();
|
|
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<>();
|
|
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 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 amountRatio = totalAmount > 0 ? (purchaseAmount / totalAmount) * 100 : 0;
|
|
|
double avgInventory = (purchaseQty + inventory) / 2.0;
|
|
double avgInventory = (purchaseQty + inventory) / 2.0;
|
|
|
double turnoverRate = avgInventory > 0 ? salesQty / avgInventory : 0;
|
|
double turnoverRate = avgInventory > 0 ? salesQty / avgInventory : 0;
|
|
@@ -260,6 +296,7 @@ public class InventoryService {
|
|
|
ProductInfo info = productInfoMap.get(sku);
|
|
ProductInfo info = productInfoMap.get(sku);
|
|
|
Map<String, Object> row = new LinkedHashMap<>();
|
|
Map<String, Object> row = new LinkedHashMap<>();
|
|
|
row.put("sku", sku);
|
|
row.put("sku", sku);
|
|
|
|
|
+ row.put("category", info != null ? info.getCategory() : "");
|
|
|
row.put("attribute", info != null ? info.getAttribute() : "");
|
|
row.put("attribute", info != null ? info.getAttribute() : "");
|
|
|
row.put("spuName", info != null ? info.getSpuName() : "");
|
|
row.put("spuName", info != null ? info.getSpuName() : "");
|
|
|
row.put("purchaseQty", (int) Math.round(purchaseQty));
|
|
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());
|
|
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;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private List<Map<String, Object>> buildSpuSummary(Map<String, double[]> purchaseSummary,
|
|
private List<Map<String, Object>> buildSpuSummary(Map<String, double[]> purchaseSummary,
|
|
|
Map<String, Double> salesSummary,
|
|
Map<String, Double> salesSummary,
|
|
|
- Map<String, ProductInfo> productInfoMap) {
|
|
|
|
|
|
|
+ Map<String, ProductInfo> productInfoMap,
|
|
|
|
|
+ Map<String, Double> assemblyOutputBySku) {
|
|
|
Map<String, SpuAggregate> aggregates = new HashMap<>();
|
|
Map<String, SpuAggregate> aggregates = new HashMap<>();
|
|
|
for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
|
|
for (Map.Entry<String, double[]> entry : purchaseSummary.entrySet()) {
|
|
|
String sku = entry.getKey();
|
|
String sku = entry.getKey();
|
|
@@ -292,6 +327,7 @@ public class InventoryService {
|
|
|
agg.purchaseQty += entry.getValue()[0];
|
|
agg.purchaseQty += entry.getValue()[0];
|
|
|
agg.purchaseAmount += entry.getValue()[1];
|
|
agg.purchaseAmount += entry.getValue()[1];
|
|
|
agg.salesQty += salesSummary.getOrDefault(sku, 0.0);
|
|
agg.salesQty += salesSummary.getOrDefault(sku, 0.0);
|
|
|
|
|
+ agg.assemblyQty += assemblyOutputBySku.getOrDefault(sku, 0.0);
|
|
|
agg.skuCount += 1;
|
|
agg.skuCount += 1;
|
|
|
if (info != null && info.getAttribute() != null && !info.getAttribute().trim().isEmpty()) {
|
|
if (info != null && info.getAttribute() != null && !info.getAttribute().trim().isEmpty()) {
|
|
|
String attr = info.getAttribute().trim();
|
|
String attr = info.getAttribute().trim();
|
|
@@ -304,7 +340,7 @@ public class InventoryService {
|
|
|
for (Map.Entry<String, SpuAggregate> entry : aggregates.entrySet()) {
|
|
for (Map.Entry<String, SpuAggregate> entry : aggregates.entrySet()) {
|
|
|
String spu = entry.getKey();
|
|
String spu = entry.getKey();
|
|
|
SpuAggregate agg = entry.getValue();
|
|
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 amountRatio = totalAmount > 0 ? (agg.purchaseAmount / totalAmount) * 100 : 0;
|
|
|
double avgInventory = (agg.purchaseQty + inventory) / 2.0;
|
|
double avgInventory = (agg.purchaseQty + inventory) / 2.0;
|
|
|
double turnoverRate = avgInventory > 0 ? agg.salesQty / avgInventory : 0;
|
|
double turnoverRate = avgInventory > 0 ? agg.salesQty / avgInventory : 0;
|
|
@@ -326,37 +362,57 @@ public class InventoryService {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private int calculateAssemblyQuantity(Set<String> finishedSkus) {
|
|
|
|
|
|
|
+ private AssemblyAggregation buildAssemblyAggregation(Set<String> finishedSkus) {
|
|
|
List<AssemblyRecord> assemblyRecords = dataLoader.getAssemblyRecords();
|
|
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()) {
|
|
if (assemblyRecords.isEmpty()) {
|
|
|
- return 0;
|
|
|
|
|
|
|
+ return new AssemblyAggregation(0, monthly, outputBySku, consumeBySku);
|
|
|
}
|
|
}
|
|
|
Map<String, Set<String>> semiToFinished = buildSemiToFinishedMap();
|
|
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;
|
|
double total = 0.0;
|
|
|
for (AssemblyRecord record : assemblyRecords) {
|
|
for (AssemblyRecord record : assemblyRecords) {
|
|
|
|
|
+ if (record == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ double qty = record.getQuantity();
|
|
|
|
|
+ if (qty <= 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ LocalDate date = record.getDate();
|
|
|
String code = record.getProductCode();
|
|
String code = record.getProductCode();
|
|
|
if (code == null || code.trim().isEmpty()) {
|
|
if (code == null || code.trim().isEmpty()) {
|
|
|
continue;
|
|
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() {
|
|
private Map<String, Set<String>> buildSemiToFinishedMap() {
|
|
@@ -420,8 +476,8 @@ public class InventoryService {
|
|
|
finished.add(code);
|
|
finished.add(code);
|
|
|
continue;
|
|
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;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
finished.add(code);
|
|
finished.add(code);
|
|
@@ -429,6 +485,25 @@ public class InventoryService {
|
|
|
return finished;
|
|
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) {
|
|
private boolean shouldIncludeSku(String code, Set<String> finishedSkus) {
|
|
|
if (code == null || code.trim().isEmpty()) {
|
|
if (code == null || code.trim().isEmpty()) {
|
|
|
return false;
|
|
return false;
|
|
@@ -481,6 +556,7 @@ public class InventoryService {
|
|
|
double purchaseQty;
|
|
double purchaseQty;
|
|
|
double purchaseAmount;
|
|
double purchaseAmount;
|
|
|
double salesQty;
|
|
double salesQty;
|
|
|
|
|
+ double assemblyQty;
|
|
|
int skuCount;
|
|
int skuCount;
|
|
|
Map<String, Integer> attributeCount = new HashMap<>();
|
|
Map<String, Integer> attributeCount = new HashMap<>();
|
|
|
|
|
|
|
@@ -511,4 +587,21 @@ public class InventoryService {
|
|
|
this.spuSummary = spuSummary;
|
|
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;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|