/*
* 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.emo;
import java.util.*;
import java.util.stream.Collectors;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.ggnet.dwoss.progress.SubMonitor;
import eu.ggnet.dwoss.stock.assist.Stocks;
import eu.ggnet.dwoss.stock.eao.StockTransactionEao;
import eu.ggnet.dwoss.stock.entity.*;
import eu.ggnet.dwoss.util.UserInfoException;
import eu.ggnet.dwoss.util.persistence.eao.DefaultEao;
import eu.ggnet.dwoss.util.validation.ValidationUtil;
import eu.ggnet.saft.api.progress.IMonitor;
@Stateless
public class StockTransactionEmo {
public static class LastCharSorter implements Comparator<String> {
// Sorts by last Character
@Override
public int compare(String o1, String o2) {
if ( o1 == null && o2 == null ) return 0;
if ( o1 == null ) return 1;
if ( o2 == null ) return -1;
if ( o2.length() < 3 || o2.length() < 3 ) return o1.compareTo(o2);
return o1.substring(o1.length() - 2).compareTo(o2.substring(o2.length() - 2));
}
}
private final static Logger L = LoggerFactory.getLogger(StockTransactionEmo.class);
@Inject
@Stocks
private EntityManager em;
private final StockLocationDiscoverer discoverer;
public StockTransactionEmo(EntityManager em) {
this.em = em;
this.discoverer = new StockLocationDiscoverer(em);
}
public StockTransactionEmo() {
this.discoverer = new StockLocationDiscoverer(em);
}
/**
* 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 t a merged parameter view.
* @param partialMonitor an optional monitor
* @return a map containing uniqueUnitIds and comments for their history.
* @throws UserInfoException
*/
public SortedMap<Integer, String> prepare(Transfer t, IMonitor partialMonitor) throws UserInfoException {
SubMonitor m = SubMonitor.convert(partialMonitor, "Preparing Transfer Transaciton", (t.getStockUnitIds().size() * 2) + 15);
m.start();
ValidationUtil.validate(t);
Stock destination = em.find(Stock.class, t.getDestinationStockId());
Stock source = null;
List<StockUnit> unhandledUnits = new ArrayList<>();
for (int unitId : t.getStockUnitIds()) {
m.worked(1, "Loading StockUnit(" + unitId + ")");
StockUnit stockUnit = em.find(StockUnit.class, unitId);
if ( stockUnit == null ) throw new UserInfoException("StockUnit " + unitId + " nicht vorhanden.");
if ( stockUnit.getStock() == null ) throw new UserInfoException(stockUnit + " nicht auf einem Lagerplatz.");
if ( source == null ) source = stockUnit.getStock();
if ( !source.equals(stockUnit.getStock()) ) throw new UserInfoException(stockUnit + " nicht auf Quelle " + source.getName() + ", wie alle anderen");
unhandledUnits.add(stockUnit);
}
L.debug("Unhandeled units {}", unhandledUnits.stream().map(StockUnit::toSimple).collect(Collectors.joining(",")));
SortedMap<Integer, String> result = new TreeMap<>();
for (int i = 0; i < unhandledUnits.size(); i += t.getMaxTransactionSize()) {
List<StockUnit> subList = unhandledUnits.subList(i, Math.min(unhandledUnits.size(), i + t.getMaxTransactionSize()));
L.debug("Eplizit Transfer {}", subList.stream().map(StockUnit::toSimple).collect(Collectors.joining(",")));
result.putAll(
prepareExplicitTransfer(
subList,
destination,
t.getArranger(),
t.getComment()
));
m.worked(t.getMaxTransactionSize());
}
m.message("committing");
m.finish();
return result;
}
private SortedMap<Integer, String> prepareExplicitTransfer(List<StockUnit> stockUnits, Stock destination, String arranger, String comment) {
String error = preValidatePrepareTransfer(stockUnits, destination);
if ( error != null ) throw new IllegalArgumentException(error);
StockTransaction st = new StockTransaction(StockTransactionType.TRANSFER);
st.setComment(comment);
StockTransactionStatus status = new StockTransactionStatus(StockTransactionStatusType.PREPARED, new Date());
status.addParticipation(new StockTransactionParticipation(StockTransactionParticipationType.ARRANGER, arranger));
st.addStatus(status);
st.setDestination(destination);
em.persist(st);
SortedMap<Integer, String> result = new TreeMap<>();
for (StockUnit stockUnit : stockUnits) {
if ( st.getSource() == null ) st.setSource(stockUnit.getStock());
StockTransactionPosition stp = new StockTransactionPosition(stockUnit);
st.addPosition(stp);
em.persist(stp);
// HINT: Don't use history.fire here untill we got rid of the .
result.put(stockUnit.getUniqueUnitId(), "Prepared on TransferTransaction(id=" + st.getId() + ",source=" + st.getSource().getName()
+ ",destination=" + st.getDestination().getName() + ") by " + arranger);
}
L.info("Created: {} with {}", st.toSimpleLine(), st.getPositions().stream().map(p -> p.getStockUnit().toSimple()).collect(Collectors.joining(",")));
return result;
}
private String preValidatePrepareTransfer(List<StockUnit> stockUnits, Stock destination) {
Stock source = null;
for (StockUnit stockUnit : stockUnits) {
if ( stockUnit.isInTransaction() )
return "StockUnit(id=" + stockUnit.getId() + ",unitId=" + stockUnit + ") is allready on Transaction(" + stockUnit.getTransaction().getId() + ")";
if ( source == null ) {
source = stockUnit.getStock();
if ( source.equals(destination) ) return "source=Stock(id=" + source.getId() + ",name=" + source.getName() + ") is equal to destination";
}
if ( !stockUnit.getStock().equals(source) )
return "StockUnit(id=" + stockUnit.getId() + ",unitId=" + stockUnit + ") is on Stock(id=" + stockUnit.getStock().getId() + ",name=" + stockUnit.getStock().getName() + ")"
+ " but source=Stock(id=" + source.getId() + ",name=" + source.getName() + ")";
}
return null;
}
public StockTransaction requestDestroyPrepared(int sourceId, String arrangerName, String comment) {
return request(StockTransactionType.DESTROY, StockTransactionStatusType.PREPARED, sourceId, null, arrangerName, comment);
}
public StockTransaction requestRollInPrepared(int destinationId, String arrangerName, String comment) {
return request(StockTransactionType.ROLL_IN, StockTransactionStatusType.PREPARED, null, destinationId, arrangerName, comment);
}
public StockTransaction requestRollOutPrepared(int sourceId, String arrangerName, String comment) {
return request(StockTransactionType.ROLL_OUT, StockTransactionStatusType.PREPARED, sourceId, null, arrangerName, comment);
}
public StockTransaction requestExternalTransferPrepare(int sourceId, int destinationId, String arrangerName, String comment) {
return request(StockTransactionType.EXTERNAL_TRANSFER, StockTransactionStatusType.PREPARED, sourceId, destinationId, arrangerName, comment);
}
/**
* Tries to bring the Transactions(ROLL_IN) into Status Complete, or deletes it if its empty.
* <p/>
* @param arrangerName the arranger
* @param transactions the transactions
* @return List of StockUnits, which are now in stock
*/
public List<StockUnit> completeRollIn(String arrangerName, List<StockTransaction> transactions) {
validate(StockTransactionType.ROLL_IN, StockTransactionStatusType.PREPARED, transactions);
List<StockUnit> result = new ArrayList<>();
for (StockTransaction transaction : transactions) {
if ( transaction.getPositions().isEmpty() ) {
// L.info("Removing Empty {}", transaction);
// transaction.setSource(null);
// transaction.setDestination(null);
// em.remove(transaction);
L.info("Ignoring Empty {}", transaction);
// TODO: Delete doesn' work, see http://overload.ahrensburg.gg-net.de/jira/browse/DW-1344
} else {
// TODO: If the addStatus succedes, but the hole transaciont fails and is rolled back, the addStatus is keept. Meaning the rollback is not successful completely.
StockTransactionStatus status = new StockTransactionStatus(StockTransactionStatusType.COMPLETED, new Date());
status.addParticipation(new StockTransactionParticipation(StockTransactionParticipationType.ARRANGER, arrangerName));
transaction.addStatus(status);
for (StockTransactionPosition position : transaction.getPositions()) {
StockUnit stockUnit = position.getStockUnit();
L.debug("RollingIn StockUnit={} of Transaction(id={})", stockUnit, transaction.getId());
position.setStockUnit(null);
discoverer.discoverAndSetLocation(stockUnit, transaction.getDestination());
if ( stockUnit != null ) result.add(stockUnit);
}
}
}
return result;
}
/**
* Completes the External Transfer to the Destination.
*
* @param arrangerName the arranger.
* @param transactions the transactions
* @return a list of StockUnits which are now on a new Stock.
*/
// TODO: Test
public List<StockUnit> completeExternalTransfer(String arrangerName, Collection<StockTransaction> transactions) {
validate(StockTransactionType.EXTERNAL_TRANSFER, StockTransactionStatusType.PREPARED, transactions);
List<StockUnit> result = new ArrayList<>();
for (StockTransaction st : transactions) {
StockTransactionStatus status = new StockTransactionStatus(StockTransactionStatusType.COMPLETED, new Date());
status.addParticipation(new StockTransactionParticipation(StockTransactionParticipationType.ARRANGER, arrangerName));
st.addStatus(status);
for (StockUnit stockUnit : st.getUnits()) {
result.add(stockUnit);
stockUnit.setPosition(null);
discoverer.discoverAndSetLocation(stockUnit, st.getDestination());
}
}
return result;
}
/**
* Tries to bring a StockTransaction(DESTROY) in to status complete.
* This will also remove all units for the persistence layer.
*
* @param arrangerName the arranger
* @param transactions the transactions.
* @return the uniqueUnitIds of the destroyed Units
*/
public List<Integer> completeDestroy(String arrangerName, List<StockTransaction> transactions) {
validate(StockTransactionType.DESTROY, StockTransactionStatusType.PREPARED, transactions);
return completeRollOutDestroy(arrangerName, transactions);
}
/**
* Tries to bring a StockTransaction(ROLL_OUT) in to status complete.
* This will also remove all units for the persistence layer.
*
* @param arrangerName the arranger
* @param transactions the transactions.
* @return the uniqueUnitIds of the rolled out Units
*/
public List<Integer> completeRollOut(String arrangerName, List<StockTransaction> transactions) {
validate(StockTransactionType.ROLL_OUT, StockTransactionStatusType.PREPARED, transactions);
return completeRollOutDestroy(arrangerName, transactions);
}
/**
* Validates if all supplied transactions have the selected type and status.
*
* @param transactionType the type to validate
* @param statusType the status to validate
* @param transactions the selected transactions
*/
private void validate(StockTransactionType transactionType, StockTransactionStatusType statusType, Collection<StockTransaction> transactions) {
if ( transactions == null ) throw new RuntimeException("No Transaction supplied");
for (StockTransaction stockTransaction : transactions) {
if ( stockTransaction.getType() != transactionType )
throw new IllegalArgumentException("Transaction not of type " + transactionType + ", " + stockTransaction);
if ( stockTransaction.getStatus().getType() != statusType )
throw new IllegalArgumentException("Transaction not in status " + statusType + ", " + stockTransaction);
}
}
/**
* Tries to bring the Transactions(ROLL_OUT or DESTROY) into Status complete and removes all attached Units from the stock.
* Also removes LogicTransactions if they have become empty.
* <p/>
* @param arrangerName the arranger
* @param transactions the transactions
* @return List of UniqueUnitIds which where assosiated with the removed stockUnits
*/
private List<Integer> completeRollOutDestroy(String arrangerName, Collection<StockTransaction> transactions) {
List<Integer> result = new ArrayList<>();
for (StockTransaction transaction : transactions) {
for (StockUnit stockUnit : transaction.getUnits()) {
stockUnit.setPosition(null);
stockUnit.setStock(null);
LogicTransaction lt = stockUnit.getLogicTransaction();
if ( lt != null ) lt.remove(stockUnit);
result.add(stockUnit.getUniqueUnitId());
em.remove(stockUnit);
if ( lt != null && lt.getUnits().isEmpty() ) em.remove(lt);
}
StockTransactionStatus status = new StockTransactionStatus(StockTransactionStatusType.COMPLETED, new Date());
status.addParticipation(new StockTransactionParticipation(StockTransactionParticipationType.ARRANGER, arrangerName));
transaction.addStatus(status);
}
return result;
}
/**
* Request a StockTransaction with the selected parameters.
*
* @param transactionType the type
* @param statusType the status
* @param sourceId the source, if null only the destination is used.
* @param destinationId the destination, if null only the source is used
* @param arrangerName the arranger
* @param comment the comment
* @return always a persisted transactions with the supplied parameters
*/
// TODO: Still not implemented to findByTypeAndStatus a transaction with source an destination. (For Transfer)
private StockTransaction request(StockTransactionType transactionType,
StockTransactionStatusType statusType,
Integer sourceId, Integer destinationId,
String arrangerName, String comment) {
StockTransactionEao stockTransactionEao = new StockTransactionEao(em);
List<StockTransaction> stockTransactions;
if ( sourceId != null ) stockTransactions = stockTransactionEao.findBySource(sourceId, transactionType, statusType, arrangerName, comment);
else stockTransactions = stockTransactionEao.findByDestination(destinationId, transactionType, statusType, arrangerName, comment);
if ( !stockTransactions.isEmpty() ) return stockTransactions.get(0);
DefaultEao<Stock> stockEao = new DefaultEao<>(Stock.class, em);
StockTransaction st = new StockTransaction(transactionType);
st.setComment(comment);
StockTransactionStatus status = new StockTransactionStatus(statusType, new Date());
status.addParticipation(new StockTransactionParticipation(StockTransactionParticipationType.ARRANGER, arrangerName));
st.addStatus(status);
if ( sourceId != null ) st.setSource(stockEao.findById(sourceId));
if ( destinationId != null ) st.setDestination(stockEao.findById(destinationId));
em.persist(st);
return st;
}
}