/** * Axelor Business Solutions * * Copyright (C) 2016 Axelor (<http://axelor.com>). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.axelor.apps.stock.service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.joda.time.LocalDate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.axelor.apps.base.db.Address; import com.axelor.apps.base.db.Company; import com.axelor.apps.base.db.IAdministration; import com.axelor.apps.base.db.Partner; import com.axelor.apps.base.service.administration.SequenceService; import com.axelor.apps.base.service.administration.GeneralService; import com.axelor.apps.stock.db.Location; import com.axelor.apps.stock.db.StockMove; import com.axelor.apps.stock.db.StockMoveLine; import com.axelor.apps.stock.db.repo.LocationRepository; import com.axelor.apps.stock.db.repo.StockMoveLineRepository; import com.axelor.apps.stock.db.repo.StockMoveManagementRepository; import com.axelor.apps.stock.db.repo.StockMoveRepository; import com.axelor.apps.stock.exception.IExceptionMessage; import com.axelor.db.JPA; import com.axelor.exception.AxelorException; import com.axelor.exception.db.IException; import com.axelor.i18n.I18n; import com.axelor.inject.Beans; import com.google.inject.Inject; import com.google.inject.persist.Transactional; public class StockMoveServiceImpl implements StockMoveService { private static final Logger LOG = LoggerFactory.getLogger(StockMoveServiceImpl.class); @Inject protected StockMoveLineService stockMoveLineService; @Inject private SequenceService sequenceService; protected LocalDate today; @Inject private StockMoveLineRepository stockMoveLineRepo; @Inject protected GeneralService generalService; @Inject protected StockMoveRepository stockMoveRepo; @Inject public StockMoveServiceImpl() { this.today = Beans.get(GeneralService.class).getTodayDate(); } @Override public BigDecimal compute(StockMove stockMove){ BigDecimal exTaxTotal = BigDecimal.ZERO; if(stockMove.getStockMoveLineList() != null && !stockMove.getStockMoveLineList().isEmpty()){ for (StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) { exTaxTotal = exTaxTotal.add(stockMoveLine.getRealQty().multiply(stockMoveLine.getUnitPriceUntaxed())); } } return exTaxTotal.setScale(2, RoundingMode.HALF_UP); } /** * Méthode permettant d'obtenir la séquence du StockMove. * @param stockMoveType Type de mouvement de stock * @param company la société * @return la chaine contenant la séquence du StockMove * @throws AxelorException Aucune séquence de StockMove n'a été configurée */ @Override public String getSequenceStockMove(int stockMoveType, Company company) throws AxelorException { String ref = ""; switch(stockMoveType) { case StockMoveRepository.TYPE_INTERNAL: ref = sequenceService.getSequenceNumber(IAdministration.INTERNAL, company); if (ref == null) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.STOCK_MOVE_1), company.getName()), IException.CONFIGURATION_ERROR); } break; case StockMoveRepository.TYPE_INCOMING: ref = sequenceService.getSequenceNumber(IAdministration.INCOMING, company); if (ref == null) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.STOCK_MOVE_2), company.getName()), IException.CONFIGURATION_ERROR); } break; case StockMoveRepository.TYPE_OUTGOING: ref = sequenceService.getSequenceNumber(IAdministration.OUTGOING, company); if (ref == null) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.STOCK_MOVE_3), company.getName()), IException.CONFIGURATION_ERROR); } break; default: throw new AxelorException(String.format(I18n.get(IExceptionMessage.STOCK_MOVE_4), company.getName()), IException.CONFIGURATION_ERROR); } return ref; } /** * Méthode générique permettant de créer un StockMove. * @param fromAddress l'adresse destination * @param toAddress l'adresse destination * @param company la société * @param clientPartner le tier client * @return l'objet StockMove * @throws AxelorException Aucune séquence de StockMove (Livraison) n'a été configurée */ @Override public StockMove createStockMove(Address fromAddress, Address toAddress, Company company, Partner clientPartner, Location fromLocation, Location toLocation, LocalDate estimatedDate, String description) throws AxelorException { return this.createStockMove(fromAddress, toAddress, company, clientPartner, fromLocation, toLocation, null, estimatedDate, description); } /** * Méthode générique permettant de créer un StockMove. * @param toAddress l'adresse destination * @param company la société * @param clientPartner le tier client * @param refSequence la séquence du StockMove * @return l'objet StockMove * @throws AxelorException Aucune séquence de StockMove (Livraison) n'a été configurée */ @Override public StockMove createStockMove(Address fromAddress, Address toAddress, Company company, Partner clientPartner, Location fromLocation, Location toLocation, LocalDate realDate, LocalDate estimatedDate, String description) throws AxelorException { StockMove stockMove = new StockMove(); stockMove.setFromAddress(fromAddress); stockMove.setToAddress(toAddress); stockMove.setCompany(company); stockMove.setStatusSelect(StockMoveRepository.STATUS_DRAFT); stockMove.setRealDate(realDate); stockMove.setEstimatedDate(estimatedDate); stockMove.setPartner(clientPartner); stockMove.setFromLocation(fromLocation); stockMove.setToLocation(toLocation); stockMove.setDescription(description); return stockMove; } @Override public int getStockMoveType(Location fromLocation, Location toLocation) { if(fromLocation.getTypeSelect() == LocationRepository.TYPE_INTERNAL && toLocation.getTypeSelect() == LocationRepository.TYPE_INTERNAL) { return StockMoveRepository.TYPE_INTERNAL; } else if(fromLocation.getTypeSelect() != LocationRepository.TYPE_INTERNAL && toLocation.getTypeSelect() == LocationRepository.TYPE_INTERNAL) { return StockMoveRepository.TYPE_INCOMING; } else if(fromLocation.getTypeSelect() == LocationRepository.TYPE_INTERNAL && toLocation.getTypeSelect() != LocationRepository.TYPE_INTERNAL) { return StockMoveRepository.TYPE_OUTGOING; } return 0; } @Override public void validate(StockMove stockMove) throws AxelorException { this.plan(stockMove); this.realize(stockMove); } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void plan(StockMove stockMove) throws AxelorException { LOG.debug("Plannification du mouvement de stock : {} ", new Object[] { stockMove.getStockMoveSeq() }); Location fromLocation = stockMove.getFromLocation(); Location toLocation = stockMove.getToLocation(); if(fromLocation == null) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.STOCK_MOVE_5), stockMove.getName()), IException.CONFIGURATION_ERROR); } if(toLocation == null) { throw new AxelorException(String.format(I18n.get(IExceptionMessage.STOCK_MOVE_6), stockMove.getName()), IException.CONFIGURATION_ERROR); } // Set the type select if(stockMove.getTypeSelect() == null || stockMove.getTypeSelect() == 0) { stockMove.setTypeSelect(this.getStockMoveType(fromLocation, toLocation)); } if(stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) { } // Set the sequence if(stockMove.getStockMoveSeq() == null || stockMove.getStockMoveSeq().isEmpty()) { stockMove.setStockMoveSeq( this.getSequenceStockMove(stockMove.getTypeSelect(), stockMove.getCompany())); } if(stockMove.getName() == null || stockMove.getName().isEmpty()) { stockMove.setName(stockMove.getStockMoveSeq()); } stockMoveLineService.updateLocations( fromLocation, toLocation, stockMove.getStatusSelect(), StockMoveRepository.STATUS_PLANNED, stockMove.getStockMoveLineList(), stockMove.getEstimatedDate(), false); if(stockMove.getEstimatedDate() == null) { stockMove.setEstimatedDate(this.today); } stockMove.setStatusSelect(StockMoveRepository.STATUS_PLANNED); stockMoveRepo.save(stockMove); } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public String realize(StockMove stockMove) throws AxelorException { LOG.debug("Réalisation du mouvement de stock : {} ", new Object[] { stockMove.getStockMoveSeq() }); String newStockSeq = null; stockMoveLineService.updateLocations( stockMove.getFromLocation(), stockMove.getToLocation(), stockMove.getStatusSelect(), StockMoveRepository.STATUS_REALIZED, stockMove.getStockMoveLineList(), stockMove.getEstimatedDate(), true); stockMove.setStatusSelect(StockMoveRepository.STATUS_REALIZED); stockMove.setRealDate(this.today); stockMoveRepo.save(stockMove); if(!stockMove.getIsWithBackorder() && !stockMove.getIsWithReturnSurplus()) return null; if(stockMove.getIsWithBackorder() && this.mustBeSplit(stockMove.getStockMoveLineList())) { StockMove newStockMove = this.copyAndSplitStockMove(stockMove); newStockSeq = newStockMove.getStockMoveSeq(); } if(stockMove.getIsWithReturnSurplus() && this.mustBeSplit(stockMove.getStockMoveLineList())) { StockMove newStockMove = this.copyAndSplitStockMoveReverse(stockMove, true); if(newStockSeq != null) newStockSeq = newStockSeq+" "+newStockMove.getStockMoveSeq(); else newStockSeq = newStockMove.getStockMoveSeq(); } return newStockSeq; } @Override public boolean mustBeSplit(List<StockMoveLine> stockMoveLineList) { for(StockMoveLine stockMoveLine : stockMoveLineList) { if(stockMoveLine.getRealQty().compareTo(stockMoveLine.getQty()) != 0) { return true; } } return false; } @Override public StockMove copyAndSplitStockMove(StockMove stockMove) throws AxelorException { StockMove newStockMove = JPA.copy(stockMove, false); for(StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) { if(stockMoveLine.getQty().compareTo(stockMoveLine.getRealQty()) > 0) { StockMoveLine newStockMoveLine = JPA.copy(stockMoveLine, false); newStockMoveLine.setQty(stockMoveLine.getQty().subtract(stockMoveLine.getRealQty())); newStockMoveLine.setRealQty(newStockMoveLine.getQty()); newStockMove.addStockMoveLineListItem(newStockMoveLine); } } newStockMove.setStatusSelect(StockMoveRepository.STATUS_PLANNED); newStockMove.setRealDate(null); newStockMove.setStockMoveSeq(this.getSequenceStockMove(newStockMove.getTypeSelect(), newStockMove.getCompany())); newStockMove.setName(newStockMove.getStockMoveSeq() + " " + I18n.get(IExceptionMessage.STOCK_MOVE_7) + " " + stockMove.getStockMoveSeq() + " )" ); return stockMoveRepo.save(newStockMove); } @Override public StockMove copyAndSplitStockMoveReverse(StockMove stockMove, boolean split) throws AxelorException { StockMove newStockMove = new StockMove(); newStockMove.setCompany(stockMove.getCompany()); newStockMove.setPartner(stockMove.getPartner()); newStockMove.setFromLocation(stockMove.getToLocation()); newStockMove.setToLocation(stockMove.getFromLocation()); newStockMove.setEstimatedDate(stockMove.getEstimatedDate()); newStockMove.setFromAddress(stockMove.getFromAddress()); if(stockMove.getToAddress() != null) newStockMove.setFromAddress(stockMove.getToAddress()); if(stockMove.getTypeSelect() == StockMoveRepository.TYPE_INCOMING) newStockMove.setTypeSelect(StockMoveRepository.TYPE_OUTGOING); if(stockMove.getTypeSelect() == StockMoveRepository.TYPE_OUTGOING) newStockMove.setTypeSelect(StockMoveRepository.TYPE_INCOMING); if(stockMove.getTypeSelect() == StockMoveRepository.TYPE_INTERNAL) newStockMove.setTypeSelect(StockMoveRepository.TYPE_INTERNAL); newStockMove.setStatusSelect(StockMoveRepository.STATUS_DRAFT); newStockMove.setStockMoveSeq(getSequenceStockMove(newStockMove.getTypeSelect(),newStockMove.getCompany())); for(StockMoveLine stockMoveLine : stockMove.getStockMoveLineList()) { if(stockMoveLine.getRealQty().compareTo(stockMoveLine.getQty()) > 0) { StockMoveLine newStockMoveLine = JPA.copy(stockMoveLine, false); if(!split) { newStockMoveLine.setQty(stockMoveLine.getRealQty().subtract(stockMoveLine.getQty())); } newStockMoveLine.setRealQty(newStockMoveLine.getQty()); newStockMove.addStockMoveLineListItem(newStockMoveLine); } } newStockMove.setStatusSelect(StockMoveRepository.STATUS_PLANNED); newStockMove.setRealDate(null); newStockMove.setStockMoveSeq(this.getSequenceStockMove(newStockMove.getTypeSelect(), newStockMove.getCompany())); newStockMove.setName(newStockMove.getStockMoveSeq() + " " + I18n.get(IExceptionMessage.STOCK_MOVE_8) + " " + stockMove.getStockMoveSeq() + " )" ); return stockMoveRepo.save(newStockMove); } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void cancel(StockMove stockMove) throws AxelorException { LOG.debug("Annulation du mouvement de stock : {} ", new Object[] { stockMove.getStockMoveSeq() }); stockMoveLineService.updateLocations( stockMove.getFromLocation(), stockMove.getToLocation(), stockMove.getStatusSelect(), StockMoveRepository.STATUS_CANCELED, stockMove.getStockMoveLineList(), stockMove.getEstimatedDate(), false); stockMove.setStatusSelect(StockMoveRepository.STATUS_CANCELED); stockMove.setRealDate(this.today); stockMoveRepo.save(stockMove); } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public Boolean splitStockMoveLinesUnit(List<StockMoveLine> stockMoveLines, BigDecimal splitQty){ Boolean selected = false; for(StockMoveLine moveLine : stockMoveLines){ if(moveLine.isSelected()){ selected = true; StockMoveLine line = stockMoveLineRepo.find(moveLine.getId()); BigDecimal totalQty = line.getQty(); LOG.debug("Move Line selected: {}, Qty: {}",new Object[]{line,totalQty}); while(splitQty.compareTo(totalQty) < 0){ totalQty = totalQty.subtract(splitQty); StockMoveLine newLine = JPA.copy(line, false); newLine.setQty(splitQty); newLine.setRealQty(splitQty); stockMoveLineRepo.save(newLine); } LOG.debug("Qty remains: {}",totalQty); if(totalQty.compareTo(BigDecimal.ZERO) > 0){ StockMoveLine newLine = JPA.copy(line, false); newLine.setQty(totalQty); newLine.setRealQty(totalQty); stockMoveLineRepo.save(newLine); LOG.debug("New line created: {}",newLine); } stockMoveLineRepo.remove(line); } } return selected; } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public Boolean splitStockMoveLinesSpecial(List<HashMap> stockMoveLines, BigDecimal splitQty){ Boolean selected = false; LOG.debug("SplitQty: {}",new Object[] {splitQty}); for(HashMap moveLine : stockMoveLines){ LOG.debug("Move line: {}",new Object[]{moveLine}); if((Boolean)(moveLine.get("selected"))){ selected = true; StockMoveLine line = stockMoveLineRepo.find(Long.parseLong(moveLine.get("id").toString())); BigDecimal totalQty = line.getQty(); LOG.debug("Move Line selected: {}, Qty: {}",new Object[]{line,totalQty}); while(splitQty.compareTo(totalQty) < 0){ totalQty = totalQty.subtract(splitQty); StockMoveLine newLine = JPA.copy(line, false); newLine.setQty(splitQty); newLine.setRealQty(splitQty); stockMoveLineRepo.save(newLine); } LOG.debug("Qty remains: {}",totalQty); if(totalQty.compareTo(BigDecimal.ZERO) > 0){ StockMoveLine newLine = JPA.copy(line, false); newLine.setQty(totalQty); newLine.setRealQty(totalQty); stockMoveLineRepo.save(newLine); LOG.debug("New line created: {}",newLine); } stockMoveLineRepo.remove(line); } } return selected; } @Transactional(rollbackOn = {AxelorException.class, Exception.class}) @Override public Long splitInto2(Long originalStockMoveId, List<StockMoveLine> stockMoveLines){ //Get original stock move StockMove originalStockMove = stockMoveRepo.find(originalStockMoveId); //Copy this stock move StockMove newStockMove = Beans.get(StockMoveManagementRepository.class).copy(originalStockMove, true); List<StockMoveLine> newStockMoveLineToRemove = new ArrayList<StockMoveLine>(); List<StockMoveLine> originalStockMoveLineToRemove = new ArrayList<StockMoveLine>(); int lineNumber = 0; for(StockMoveLine moveLine : stockMoveLines){ if (BigDecimal.ZERO.compareTo(moveLine.getQty()) == 0){ //Remove stock move line from new stock move newStockMoveLineToRemove.add(newStockMove.getStockMoveLineList().get(lineNumber)); }else{ //Set quantity in new stock move newStockMove.getStockMoveLineList().get(lineNumber).setQty(moveLine.getQty()); newStockMove.getStockMoveLineList().get(lineNumber).setRealQty(moveLine.getQty()); //Update quantity in original stock move. //If the remaining quantity is 0, remove the stock move line StockMoveLine currentOriginalStockMoveLine = originalStockMove.getStockMoveLineList().get(lineNumber); BigDecimal remainingQty = currentOriginalStockMoveLine.getQty().subtract(moveLine.getQty()); if (BigDecimal.ZERO.compareTo(remainingQty) == 0){ //Remove the stock move line originalStockMoveLineToRemove.add(currentOriginalStockMoveLine); }else{ currentOriginalStockMoveLine.setQty(remainingQty); currentOriginalStockMoveLine.setRealQty(remainingQty); } } lineNumber++; } for (StockMoveLine stockMoveLineToRemove : newStockMoveLineToRemove) { newStockMove.getStockMoveLineList().remove(stockMoveLineToRemove); } if (!newStockMove.getStockMoveLineList().isEmpty()){ //Update original stock move for (StockMoveLine stockMoveLineToRemove : originalStockMoveLineToRemove) { originalStockMove.getStockMoveLineList().remove(stockMoveLineToRemove); } stockMoveRepo.save(originalStockMove); //Save new stock move return stockMoveRepo.save(newStockMove).getId(); }else{ return null; } } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void copyQtyToRealQty(StockMove stockMove){ for(StockMoveLine line : stockMove.getStockMoveLineList()) line.setRealQty(line.getQty()); stockMoveRepo.save(stockMove); } @Override @Transactional(rollbackOn = {AxelorException.class, Exception.class}) public void generateReversion(StockMove stockMove) throws AxelorException { LOG.debug("Creation d'un mouvement de stock inverse pour le mouvement de stock: {} ", new Object[] { stockMove.getStockMoveSeq() }); stockMoveRepo.save(this.copyAndSplitStockMoveReverse(stockMove, false)); } }