|
|
@@ -113,6 +113,19 @@ 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());
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Set<String>> semiToFinished = buildSemiToFinishedMap();
|
|
|
|
|
|
Map<YearMonth, Double> purchaseMonthly = new TreeMap<>();
|
|
|
for (PurchaseRecord record : purchaseRecords) {
|
|
|
@@ -134,9 +147,23 @@ public class InventoryService {
|
|
|
salesMonthly.merge(month, record.getQuantity(), Double::sum);
|
|
|
}
|
|
|
|
|
|
+ Map<YearMonth, Double> assemblyMonthly = new TreeMap<>();
|
|
|
+ for (AssemblyRecord record : assemblyRecords) {
|
|
|
+ LocalDate date = record.getDate();
|
|
|
+ if (date == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!isAssemblyForFinished(record, finishedSkus, semiToFinished)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ YearMonth month = YearMonth.from(date);
|
|
|
+ assemblyMonthly.merge(month, record.getQuantity(), Double::sum);
|
|
|
+ }
|
|
|
+
|
|
|
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);
|
|
|
|
|
|
@@ -144,17 +171,26 @@ public class InventoryService {
|
|
|
List<Integer> purchase = new ArrayList<>();
|
|
|
List<Integer> sales = new ArrayList<>();
|
|
|
List<Integer> inventory = new ArrayList<>();
|
|
|
- int cumulativeInventory = 0;
|
|
|
+ double cumulativeInventory = 0.0;
|
|
|
|
|
|
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.getOrDefault(month, 0.0);
|
|
|
+ cumulativeInventory += (purchaseQty + assemblyQty - salesQty);
|
|
|
|
|
|
months.add(month.toString());
|
|
|
purchase.add((int) Math.round(purchaseQty));
|
|
|
sales.add((int) Math.round(salesQty));
|
|
|
- inventory.add(cumulativeInventory);
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
|
@@ -327,35 +363,46 @@ public class InventoryService {
|
|
|
return 0;
|
|
|
}
|
|
|
Map<String, Set<String>> semiToFinished = buildSemiToFinishedMap();
|
|
|
- if (semiToFinished.isEmpty() || finishedSkus == null || finishedSkus.isEmpty()) {
|
|
|
+ if (finishedSkus == null || finishedSkus.isEmpty()) {
|
|
|
return (int) Math.round(assemblyRecords.stream().mapToDouble(AssemblyRecord::getQuantity).sum());
|
|
|
}
|
|
|
double total = 0.0;
|
|
|
for (AssemblyRecord record : assemblyRecords) {
|
|
|
- String code = record.getProductCode();
|
|
|
- if (code == null || code.trim().isEmpty()) {
|
|
|
+ if (!isAssemblyForFinished(record, finishedSkus, semiToFinished)) {
|
|
|
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();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ 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();
|