From 5181f5c2589b47e7e6a25bf4879f73cee568fa92 Mon Sep 17 00:00:00 2001 From: Kheireddine Mehdi Date: Wed, 7 Jan 2026 11:44:14 +0100 Subject: [PATCH] feat(inventory): consolidate inventory lines Group inventory lines by location, product, unit and tracking Number Optionally include internal tracking number Create consolidated line and archive originals Preserve counting data (value, user, date) --- .../stock/service/InventoryLineService.java | 374 +++++++++++++++++- .../stock/web/InventoryLineController.java | 28 ++ .../main/resources/domains/InventoryLine.xml | 1 + 3 files changed, 402 insertions(+), 1 deletion(-) diff --git a/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/service/InventoryLineService.java b/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/service/InventoryLineService.java index 0fc22eb..147d382 100644 --- a/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/service/InventoryLineService.java +++ b/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/service/InventoryLineService.java @@ -27,6 +27,7 @@ import com.axelor.apps.stock.db.TrackingNumber; import com.axelor.apps.stock.db.repo.InventoryLineRepository; import com.axelor.apps.stock.db.repo.TrackingNumberRepository; import com.axelor.auth.AuthUtils; +import com.axelor.auth.db.User; import com.axelor.db.Query; import com.axelor.inject.Beans; import com.google.inject.Inject; @@ -34,12 +35,17 @@ import com.google.inject.persist.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class InventoryLineService { @Inject private ProductRepository productRepository; private TrackingNumberRepository trackingNumberRepository; - private InventoryLineRepository inventoryLineRepository; + @Inject private InventoryLineRepository inventoryLineRepository; public InventoryLine createInventoryLine( Inventory inventory, @@ -171,4 +177,370 @@ public class InventoryLineService { Beans.get(InventoryLineRepository.class).save(line); } + + @Transactional + public void consolidateInventoryLines(StockLocation stockLocation, Boolean addInternalTrackingNumber) { + + List inventoryLines = + inventoryLineRepository + .all() + .filter( + "self.stockLocation = ?1 " + + //"AND (self.isConsolidate = false OR self.isConsolidate is null) " + + "AND (self.archived = false OR self.archived is null)", + stockLocation + ) + .fetch(); + + System.out.println("************* lines" + inventoryLines); + Map> groupedLines = + inventoryLines.stream() + .filter(line -> line.getProductName() != null) + .filter(line -> + !addInternalTrackingNumber || + line.getInternalTrackingNumberStr() == null + ) + .collect(Collectors.groupingBy(line -> { + + String key = + line.getStockLocation().getId() + "|" + + (line.getProduct() != null + ? line.getProduct().getId() + : line.getProductName().trim().toUpperCase()) + "|" + + (line.getUnit() != null ? line.getUnit().getId() : "NULL") + "|" + + (line.getTrackingNumber() != null + ? line.getTrackingNumber().getId() + : "NULL") + "|" + + (line.getTrackingNumberStr() != null + ? line.getTrackingNumberStr().trim().toUpperCase() + : "NULL"); + + if (addInternalTrackingNumber) { + key += "|" + + (line.getInternalTrackingNumberStr() != null + ? line.getInternalTrackingNumberStr().trim().toUpperCase() + : "NULL"); + } + + return key; + })); + + List consolidatedInventoryLines = new ArrayList<>(); + System.out.println("************* lines" + groupedLines); + for (List lines : groupedLines.values()) { + + if (lines.size() <= 1) { + continue; + } + + InventoryLine consolidated1 = lines.stream() + .filter(InventoryLine::getIsConsolidate) + .findFirst() + .orElse(null); + + boolean hasConsolidatedLine = consolidated1 != null; + + + if(!hasConsolidatedLine){ + + InventoryLine first = lines.get(0); + + BigDecimal firstCounting = BigDecimal.ZERO; + User firstCountingByUser = null; + LocalDateTime firstCountingDate = null; + + BigDecimal secondCounting = BigDecimal.ZERO; + User secondCountingByUser = null; + LocalDateTime secondCountingDate = null; + + BigDecimal thirdCounting = BigDecimal.ZERO; + User thirdCountingByUser = null; + LocalDateTime thirdCountingDate = null; + + BigDecimal forthCounting = BigDecimal.ZERO; + User forthCountingByUser = null; + LocalDateTime forthCountingDate = null; + + BigDecimal controlCounting = BigDecimal.ZERO; + User controlCountingByUser = null; + LocalDateTime controlCountingDate = null; + + BigDecimal fifthCounting = BigDecimal.ZERO; + User fifthCountingByUser = null; + LocalDateTime fifthCountingDate = null; + + BigDecimal sixthCounting = BigDecimal.ZERO; + User sixthCountingByUser = null; + LocalDateTime sixthCountingDate = null; + + for (InventoryLine line : lines) { + if (line.getFirstCounting() != null) { + firstCounting = firstCounting.add(line.getFirstCounting()); + if (firstCountingByUser == null) { + firstCountingByUser = line.getFirstCountingByUser(); + firstCountingDate = line.getFirstCountingDate(); + } + } + + if (line.getSecondCounting() != null) { + secondCounting = secondCounting.add(line.getSecondCounting()); + if (secondCountingByUser == null) { + secondCountingByUser = line.getSecondCountingByUser(); + secondCountingDate = line.getSecondCountingDate(); + } + } + + if (line.getThirdCounting() != null) { + thirdCounting = thirdCounting.add(line.getThirdCounting()); + if (thirdCountingByUser == null) { + thirdCountingByUser = line.getThirdCountingByUser(); + thirdCountingDate = line.getThirdCountingDate(); + } + } + + if (line.getForthCounting() != null) { + forthCounting = forthCounting.add(line.getForthCounting()); + if (forthCountingByUser == null) { + forthCountingByUser = line.getForthCountingByUser(); + forthCountingDate = line.getForthCountingDate(); + } + } + + if (line.getControlCounting() != null) { + controlCounting = controlCounting.add(line.getControlCounting()); + if (controlCountingByUser == null) { + controlCountingByUser = line.getControlCountingByUser(); + controlCountingDate = line.getControlCountingDate(); + } + } + + if (line.getFifthCounting() != null) { + fifthCounting = fifthCounting.add(line.getFifthCounting()); + if (fifthCountingByUser == null) { + fifthCountingByUser = line.getFifthCountingByUser(); + fifthCountingDate = line.getFifthCountingDate(); + } + } + + if (line.getSixthCounting() != null) { + sixthCounting = sixthCounting.add(line.getSixthCounting()); + if (sixthCountingByUser == null) { + sixthCountingByUser = line.getSixthCountingByUser(); + sixthCountingDate = line.getSixthCountingDate(); + } + } + } + + InventoryLine consolidated = new InventoryLine(); + consolidated.setStockLocation(first.getStockLocation()); + if(first.getProduct() != null) + consolidated.setProduct(first.getProduct()); + consolidated.setProductName(first.getProductName().trim().toUpperCase()); + if(first.getUnit() != null) + consolidated.setUnit(first.getUnit()); + if (first.getTrackingNumber() != null) { + consolidated.setTrackingNumber(first.getTrackingNumber()); + } + + if (first.getTrackingNumberStr() != null) { + consolidated.setTrackingNumberStr(first.getTrackingNumberStr().trim().toUpperCase()); + } + + if (addInternalTrackingNumber && first.getInternalTrackingNumberStr() != null) + consolidated.setInternalTrackingNumberStr(first.getInternalTrackingNumberStr().trim().toUpperCase()); + + consolidated.setFirstCounting(firstCounting); + consolidated.setFirstCountingByUser(firstCountingByUser); + consolidated.setFirstCountingDate(firstCountingDate); + + consolidated.setSecondCounting(secondCounting); + consolidated.setSecondCountingByUser(secondCountingByUser); + consolidated.setSecondCountingDate(secondCountingDate); + + consolidated.setThirdCounting(thirdCounting); + consolidated.setThirdCountingByUser(thirdCountingByUser); + consolidated.setThirdCountingDate(thirdCountingDate); + + consolidated.setForthCounting(forthCounting); + consolidated.setForthCountingByUser(forthCountingByUser); + consolidated.setForthCountingDate(forthCountingDate); + + consolidated.setControlCounting(controlCounting); + consolidated.setControlCountingByUser(controlCountingByUser); + consolidated.setControlCountingDate(controlCountingDate); + + consolidated.setFifthCounting(fifthCounting); + consolidated.setFifthCountingByUser(fifthCountingByUser); + consolidated.setFifthCountingDate(fifthCountingDate); + + consolidated.setSixthCounting(sixthCounting); + consolidated.setSixthCountingByUser(sixthCountingByUser); + consolidated.setSixthCountingDate(sixthCountingDate); + + + consolidated.setIsConsolidate(true); + + inventoryLineRepository.save(consolidated); + + // Archive old lines + for (InventoryLine line : lines) { + line.setArchived(true); + inventoryLineRepository.save(line); + } + } else { + + InventoryLine consolidated = consolidated1; + + BigDecimal firstCounting = + consolidated.getFirstCounting() != null + ? consolidated.getFirstCounting() + : BigDecimal.ZERO; + + BigDecimal secondCounting = + consolidated.getSecondCounting() != null + ? consolidated.getSecondCounting() + : BigDecimal.ZERO; + + BigDecimal thirdCounting = + consolidated.getThirdCounting() != null + ? consolidated.getThirdCounting() + : BigDecimal.ZERO; + + BigDecimal forthCounting = + consolidated.getForthCounting() != null + ? consolidated.getForthCounting() + : BigDecimal.ZERO; + + BigDecimal controlCounting = + consolidated.getControlCounting() != null + ? consolidated.getControlCounting() + : BigDecimal.ZERO; + + BigDecimal fifthCounting = + consolidated.getFifthCounting() != null + ? consolidated.getFifthCounting() + : BigDecimal.ZERO; + + BigDecimal sixthCounting = + consolidated.getSixthCounting() != null + ? consolidated.getSixthCounting() + : BigDecimal.ZERO; + + User firstCountingByUser = consolidated.getFirstCountingByUser(); + LocalDateTime firstCountingDate = consolidated.getFirstCountingDate(); + + User secondCountingByUser = consolidated.getSecondCountingByUser(); + LocalDateTime secondCountingDate = consolidated.getSecondCountingDate(); + + User thirdCountingByUser = consolidated.getThirdCountingByUser(); + LocalDateTime thirdCountingDate = consolidated.getThirdCountingDate(); + + User forthCountingByUser = consolidated.getForthCountingByUser(); + LocalDateTime forthCountingDate = consolidated.getForthCountingDate(); + + User controlCountingByUser = consolidated.getControlCountingByUser(); + LocalDateTime controlCountingDate = consolidated.getControlCountingDate(); + + User fifthCountingByUser = consolidated.getFifthCountingByUser(); + LocalDateTime fifthCountingDate = consolidated.getFifthCountingDate(); + + User sixthCountingByUser = consolidated.getSixthCountingByUser(); + LocalDateTime sixthCountingDate = consolidated.getSixthCountingDate(); + + for (InventoryLine line : lines) { + + if (line.getIsConsolidate()) { + continue; // skip the consolidated line itself + } + + if (line.getFirstCounting() != null) { + firstCounting = firstCounting.add(line.getFirstCounting()); + if (firstCountingByUser == null) { + firstCountingByUser = line.getFirstCountingByUser(); + firstCountingDate = line.getFirstCountingDate(); + } + } + + if (line.getSecondCounting() != null) { + secondCounting = secondCounting.add(line.getSecondCounting()); + if (secondCountingByUser == null) { + secondCountingByUser = line.getSecondCountingByUser(); + secondCountingDate = line.getSecondCountingDate(); + } + } + + if (line.getThirdCounting() != null) { + thirdCounting = thirdCounting.add(line.getThirdCounting()); + if (thirdCountingByUser == null) { + thirdCountingByUser = line.getThirdCountingByUser(); + thirdCountingDate = line.getThirdCountingDate(); + } + } + + if (line.getForthCounting() != null) { + forthCounting = forthCounting.add(line.getForthCounting()); + if (forthCountingByUser == null) { + forthCountingByUser = line.getForthCountingByUser(); + forthCountingDate = line.getForthCountingDate(); + } + } + + if (line.getControlCounting() != null) { + controlCounting = controlCounting.add(line.getControlCounting()); + if (controlCountingByUser == null) { + controlCountingByUser = line.getControlCountingByUser(); + controlCountingDate = line.getControlCountingDate(); + } + } + + if (line.getFifthCounting() != null) { + fifthCounting = fifthCounting.add(line.getFifthCounting()); + if (fifthCountingByUser == null) { + fifthCountingByUser = line.getFifthCountingByUser(); + fifthCountingDate = line.getFifthCountingDate(); + } + } + + if (line.getSixthCounting() != null) { + sixthCounting = sixthCounting.add(line.getSixthCounting()); + if (sixthCountingByUser == null) { + sixthCountingByUser = line.getSixthCountingByUser(); + sixthCountingDate = line.getSixthCountingDate(); + } + } + line.setArchived(true); + inventoryLineRepository.save(line); + } + + consolidated.setFirstCounting(firstCounting); + consolidated.setFirstCountingByUser(firstCountingByUser); + consolidated.setFirstCountingDate(firstCountingDate); + + consolidated.setSecondCounting(secondCounting); + consolidated.setSecondCountingByUser(secondCountingByUser); + consolidated.setSecondCountingDate(secondCountingDate); + + consolidated.setThirdCounting(thirdCounting); + consolidated.setThirdCountingByUser(thirdCountingByUser); + consolidated.setThirdCountingDate(thirdCountingDate); + + consolidated.setForthCounting(forthCounting); + consolidated.setForthCountingByUser(forthCountingByUser); + consolidated.setForthCountingDate(forthCountingDate); + + consolidated.setControlCounting(controlCounting); + consolidated.setControlCountingByUser(controlCountingByUser); + consolidated.setControlCountingDate(controlCountingDate); + + consolidated.setFifthCounting(fifthCounting); + consolidated.setFifthCountingByUser(fifthCountingByUser); + consolidated.setFifthCountingDate(fifthCountingDate); + + consolidated.setSixthCounting(sixthCounting); + consolidated.setSixthCountingByUser(sixthCountingByUser); + consolidated.setSixthCountingDate(sixthCountingDate); + inventoryLineRepository.save(consolidated); + } + } + } } diff --git a/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/web/InventoryLineController.java b/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/web/InventoryLineController.java index 885a2a3..6c81f94 100644 --- a/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/web/InventoryLineController.java +++ b/modules/axelor-open-suite/axelor-stock/src/main/java/com/axelor/apps/stock/web/InventoryLineController.java @@ -22,9 +22,11 @@ import com.axelor.apps.base.db.repo.ProductRepository; import com.axelor.apps.stock.db.Inventory; import com.axelor.apps.stock.db.InventoryLine; import com.axelor.apps.stock.db.StockLocationLine; +import com.axelor.apps.stock.db.StockLocation; import com.axelor.apps.stock.db.TrackingNumber; import com.axelor.apps.stock.db.repo.InventoryLineRepository; import com.axelor.apps.stock.db.repo.InventoryRepository; +import com.axelor.apps.stock.db.repo.StockLocationRepository; import com.axelor.apps.stock.db.repo.TrackingNumberRepository; import com.axelor.apps.stock.service.InventoryLineService; import com.axelor.exception.AxelorException; @@ -38,6 +40,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import java.math.BigDecimal; import java.util.Map; +import java.util.LinkedHashMap; @Singleton public class InventoryLineController { @@ -221,4 +224,29 @@ public class InventoryLineController { response.setFlash("Updated Successfully"); // response.setReload(true); } + + public void consolidateInventoryLines(ActionRequest request, ActionResponse response){ + + Object object = request.getContext().get("stockLocation"); + LinkedHashMap stockLocationMap = (LinkedHashMap) object; + Integer stockLocationIdInt = (Integer) stockLocationMap.get("id"); + Long stockLocationId = stockLocationIdInt.longValue(); + StockLocation stockLocation = Beans.get(StockLocationRepository.class).find(stockLocationId); + + Beans.get(InventoryLineService.class).consolidateInventoryLines(stockLocation,false); + response.setFlash("Updated Successfully"); + } + + public void consolidateInventoryLinesWithInternalTrackingNumber(ActionRequest request, ActionResponse response){ + + + Object object = request.getContext().get("stockLocation"); + LinkedHashMap stockLocationMap = (LinkedHashMap) object; + Integer stockLocationIdInt = (Integer) stockLocationMap.get("id"); + Long stockLocationId = stockLocationIdInt.longValue(); + StockLocation stockLocation = Beans.get(StockLocationRepository.class).find(stockLocationId); + + Beans.get(InventoryLineService.class).consolidateInventoryLines(stockLocation,true); + response.setFlash("Updated Successfully"); + } } diff --git a/modules/axelor-open-suite/axelor-stock/src/main/resources/domains/InventoryLine.xml b/modules/axelor-open-suite/axelor-stock/src/main/resources/domains/InventoryLine.xml index edd25be..a23a130 100644 --- a/modules/axelor-open-suite/axelor-stock/src/main/resources/domains/InventoryLine.xml +++ b/modules/axelor-open-suite/axelor-stock/src/main/resources/domains/InventoryLine.xml @@ -59,6 +59,7 @@ +