/*
* Copyright (C) 2014 GG-Net GmbH - Oliver Günther
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.ggnet.dwoss.stock;
import java.util.*;
import java.util.stream.Collectors;
import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.validation.*;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.ggnet.dwoss.event.SalesChannelChange;
import eu.ggnet.dwoss.event.UnitHistory;
import eu.ggnet.dwoss.mandator.api.value.Mandator;
import eu.ggnet.dwoss.progress.MonitorFactory;
import eu.ggnet.dwoss.progress.SubMonitor;
import eu.ggnet.dwoss.rules.SalesChannel;
import eu.ggnet.dwoss.stock.assist.Stocks;
import eu.ggnet.dwoss.stock.eao.StockTransactionEao;
import eu.ggnet.dwoss.stock.eao.StockUnitEao;
import eu.ggnet.dwoss.stock.emo.*;
import eu.ggnet.dwoss.stock.entity.*;
import eu.ggnet.dwoss.util.UserInfoException;
import eu.ggnet.dwoss.util.validation.ConstraintViolationFormater;
import static eu.ggnet.dwoss.stock.entity.StockTransactionParticipationType.*;
import static eu.ggnet.dwoss.stock.entity.StockTransactionStatusType.*;
/**
* Implementation of StockTransctionProcessor.
*
* @author oliver.guenther
*/
// TODO: Look up methodes here and in the StockTransactionEmo. I assume there some overlays.
@Stateless
public class StockTransactionProcessorOperation implements StockTransactionProcessor {
private final static Logger L = LoggerFactory.getLogger(StockTransactionProcessorOperation.class);
private final static Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
@Inject
@Stocks
private EntityManager stockEm;
@Inject
private Event<UnitHistory> history;
@Inject
private Event<SalesChannelChange> channelChanger;
@Inject
private MonitorFactory monitorFactory;
@Inject
private Mandator mandator;
/**
* Rolls all StockTransaction in, completing them and setting the Stock.
*
* @param detachtedTransactions the transactions
* @param arranger the arranger
*/
@Override
public List<Integer> rollIn(List<StockTransaction> detachtedTransactions, String arranger) {
SubMonitor m = monitorFactory.newSubMonitor("RollIn", detachtedTransactions.size() * 2);
StockTransactionEao stockTransactionEao = new StockTransactionEao(stockEm);
StockTransactionEmo stockTransactionEmo = new StockTransactionEmo(stockEm);
List<StockTransaction> transactions = new ArrayList<>();
m.message("loading Transactions");
for (StockTransaction detachedTransaction : detachtedTransactions) {
transactions.add(stockTransactionEao.findById(detachedTransaction.getId()));
m.worked(1);
}
m.setWorkRemaining(3);
m.message("rolling in");
List<StockUnit> stockUnits = stockTransactionEmo.completeRollIn(arranger, transactions);
m.worked(2, "adding History");
for (StockUnit stockUnit : stockUnits) {
if ( mandator.isApplyDefaultChannelOnRollIn() ) {
SalesChannel channel = stockUnit.getStock().getPrimaryChannel();
channelChanger.fire(new SalesChannelChange(stockUnit.getUniqueUnitId(), channel));
history.fire(new UnitHistory(stockUnit.getUniqueUnitId(), "Rolled in " + stockUnit.getStock().getName() + " with " + channel.getName(), arranger));
} else {
history.fire(new UnitHistory(stockUnit.getUniqueUnitId(), "Rolled in " + stockUnit.getStock().getName(), arranger));
}
}
m.finish();
return stockUnits.stream().map(x -> x.getId()).collect(Collectors.toList());
}
/**
* Prepares the transfer of multiple units.
* Creates an amount of needed transactions in the form,
* - that the transactions are correct (all units of a transaction have the same source as the transaciton)
* - that no transaction has more units than maxUnitSize.
* <p/>
* @param stockUnits the stockUnits to transfer
* @param arranger the arranger
* @param comment a optional comment
* @return A map with uniqueUnitIds and comments for their history.
* @throws UserInfoException
*/
@Override
public SortedMap<Integer, String> perpareTransfer(List<StockUnit> stockUnits, int destinationStockId, String arranger, String comment) throws UserInfoException {
if ( stockUnits == null || stockUnits.isEmpty() ) return new TreeMap<>();
return new StockTransactionEmo(stockEm).prepare(Transfer.builder()
.destinationStockId(destinationStockId)
.stockUnitIds(stockUnits.stream().map(StockUnit::getId).collect(Collectors.toList()))
.arranger(arranger)
.comment(comment)
.maxTransactionSize(10)
.build(), monitorFactory.newSubMonitor("Umfuhr vorbereiten"));
}
/**
* Remove the stockUnit represented by the refurbishId from a stock transaction, if that transaction exists and is in state prepared.
* <p/>
* @param refurbishId the refurbishId
* @param arranger the arranger
* @param comment a optional comment
* @throws UserInfoException if no unit exists, the unit is not on a transaction or the transaction has another state then prepared.
*/
@Override
public void removeFromPreparedTransaction(final String refurbishId, final String arranger, final String comment) throws UserInfoException {
StockUnit stockUnit = new StockUnitEao(stockEm).findByRefurbishId(refurbishId);
if ( stockUnit == null ) throw new UserInfoException("SopoNr: " + refurbishId + " existiert nicht.");
if ( !stockUnit.isInTransaction() ) throw new UserInfoException("SopoNr: " + refurbishId + " nicht in Transaction.");
StockTransaction transaction = stockUnit.getTransaction();
if ( transaction.getStatus().getType() != PREPARED ) {
throw new UserInfoException("SopoNr: " + refurbishId + " auf Transaction, aber Transaction(" + transaction.getId() + ") not in Status prepared");
}
// The one case we remove the position as well, but dont need to set the stock because of beeing in status prepared
StockTransactionPosition position = stockUnit.getPosition();
stockEm.remove(position);
transaction.setComment(transaction.getComment() + ", Unit " + stockUnit.getRefurbishId() + " removed by " + arranger + ", cause=" + comment);
history.fire(new UnitHistory(stockUnit.getUniqueUnitId(), "Unit returned to Stock(" + transaction.getSource().getId() + ") " + transaction.getSource().getName()
+ ", removed from Transaction, cause: " + comment, arranger));
L.info("{} removed from {}", stockUnit, transaction);
}
/**
* Cancels a stock transaction.
* <p/>
* @param transaction the transaction to cancel.
* @param arranger the arranger
* @param comment a comment to describe why
* @throws UserInfoException if the transaction is not in state prepared.
*/
@Override
public void cancel(StockTransaction transaction, final String arranger, final String comment) throws UserInfoException {
transaction = stockEm.find(StockTransaction.class, transaction.getId());
if ( transaction.getStatus().getType() != PREPARED ) {
throw new UserInfoException("Supplied transaction is not in state prepared, but " + transaction.getStatus() + ", cancel not allowed");
}
StockTransactionStatus status = new StockTransactionStatus(CANCELLED, new Date(), comment);
status.addParticipation(new StockTransactionParticipation(ARRANGER, arranger));
transaction.addStatus(status);
for (StockUnit stockUnit : transaction.getUnits()) {
history.fire(new UnitHistory(stockUnit.getUniqueUnitId(), "Unit returned to Stock(" + transaction.getSource().getId() + ") "
+ transaction.getSource().getName() + ", cancelled Transaction(" + transaction.getId() + ")", arranger));
L.info("cancelTransaction(): Returning {} to Stock {} ", stockUnit, transaction.getSource());
stockUnit.setPosition(null);
// Nothing to do because of special case prepared transaction, which allows the unit to be on transaction and on stock
}
}
/**
* Bring a list of transactions from prepared into the state in transfer via commission.
* <p/>
* @param transactions the transaction to commission
* @param picker the pricker of units
* @param deliverer the transferer.
* @throws RuntimeException if the transaction is in some form invalid.
*/
// TODO: how about some validations.
@Override
public void commission(List<StockTransaction> transactions, String picker, String deliverer) throws RuntimeException {
for (StockTransaction transaction : transactions) {
L.info("Commissioning {}", transaction);
Set<ConstraintViolation<StockTransaction>> violations = VALIDATOR.validate(transaction);
if ( !violations.isEmpty() )
throw new RuntimeException("Invalid StockTransaction in PRE-Validate: " + ConstraintViolationFormater.toSingleLine(violations));
transaction = stockEm.find(StockTransaction.class, transaction.getId());
Date before = transaction.addStatus(COMMISSIONED, PICKER, picker, DELIVERER, deliverer);
transaction.addStatus(DateUtils.addSeconds(before, 1), IN_TRANSFER, DELIVERER, deliverer);
violations = VALIDATOR.validate(transaction);
if ( !violations.isEmpty() )
throw new RuntimeException("Invalid StockTransaction in POST-Validate: " + ConstraintViolationFormater.toSingleLine(violations));
for (StockUnit stockUnit : transaction.getUnits()) {
stockUnit.setStock(null);
}
}
}
/**
* Receive a list of transactions in the destination stock.
* <p/>
* @param transactions the transactions to receive.
* @param deliverer the deliverer
* @param reciever the receiver
*/
@Override
public void receive(List<StockTransaction> transactions, String deliverer, String reciever) {
StockLocationDiscoverer discoverer = new StockLocationDiscoverer(stockEm);
for (StockTransaction transaction : transactions) {
transaction = stockEm.find(StockTransaction.class, transaction.getId());
transaction.addStatus(RECEIVED, DELIVERER, deliverer, RECEIVER, reciever);
L.info("Receiving {} in {}", transaction, transaction.getDestination());
Stock destination = transaction.getDestination();
for (StockUnit stockUnit : transaction.getUnits()) {
stockUnit.setPosition(null);
discoverer.discoverAndSetLocation(stockUnit, destination);
history.fire(new UnitHistory(stockUnit.getUniqueUnitId(), "Unit received in Stock(" + destination.getId() + ") " + destination.getName(), reciever));
}
}
}
}