/* * 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.redtape.workflow; import eu.ggnet.dwoss.mandator.api.value.PostLedger; import eu.ggnet.dwoss.mandator.api.value.RepaymentCustomers; import eu.ggnet.dwoss.rules.TradeName; import eu.ggnet.dwoss.rules.PositionType; import eu.ggnet.dwoss.rules.DocumentType; import eu.ggnet.dwoss.mandator.api.value.Mandator; import eu.ggnet.dwoss.redtape.entity.PositionBuilder; import eu.ggnet.dwoss.redtape.entity.Dossier; import eu.ggnet.dwoss.redtape.entity.DocumentHistory; import eu.ggnet.dwoss.redtape.entity.Position; import eu.ggnet.dwoss.redtape.entity.Document; import eu.ggnet.dwoss.stock.emo.StockTransactionEmo; import eu.ggnet.dwoss.stock.entity.StockUnit; import eu.ggnet.dwoss.stock.emo.LogicTransactionEmo; import eu.ggnet.dwoss.stock.entity.StockTransaction; import java.util.*; import javax.inject.Inject; import javax.persistence.*; import eu.ggnet.dwoss.redtape.assist.RedTapes; import eu.ggnet.dwoss.redtape.eao.DocumentEao; import eu.ggnet.dwoss.redtape.format.DocumentFormater; import eu.ggnet.dwoss.stock.assist.Stocks; import eu.ggnet.dwoss.stock.eao.StockUnitEao; import eu.ggnet.dwoss.uniqueunit.assist.UniqueUnits; import eu.ggnet.dwoss.uniqueunit.eao.UniqueUnitEao; import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit; /** * The CreditMemo Workflow. * * @author oliver.guenther */ public class RedTapeUpdateRepaymentWorkflow extends RedTapeWorkflow { @Inject private RedTapeCreateDossierWorkflow createWorkflow; @Inject private RepaymentCustomers repaymentCustomers; @Inject private PostLedger postLedger; @Inject public RedTapeUpdateRepaymentWorkflow( @RedTapes EntityManager redTapeEm, @UniqueUnits EntityManager uniqueUnitEm, @Stocks EntityManager stockEm, Mandator mandator) { super(redTapeEm, uniqueUnitEm, stockEm, mandator); } protected void validate(Document altered, Document previous, Integer destinationId) { super.validate(altered, previous); if ( altered.getType() == DocumentType.ANNULATION_INVOICE ) { for (Position position : altered.getPositions().values()) { if ( position.getType() == PositionType.COMMENT ) continue; if ( position.getPrice() > 0 ) throw new IllegalArgumentException("Position has a price higher than 0: " + position); } } if ( altered.getType() != previous.getType() && altered.containsPositionType(PositionType.UNIT) && destinationId == null ) { throw new NullPointerException("DestinationId must not be null"); } } /** * If referenced Units are still in Stock, remove them form the LogicTransaction and add them to the instance variable (stockUnits). * * @return the list of removed StockUnits */ List<StockUnit> optionalRemoveFromLogicTransaction(Document document) { List<StockUnit> result = new LogicTransactionEmo(stockEm).optionalRemoveUnits( document.getDossier().getId(), document.getPositionsUniqueUnitIds()); if ( !result.isEmpty() ) L.info("Removed from LogicTransaction: {}", toIds(result)); return result; } /** * If referenced Units are not in Stock, roll them in and append them to the instance variable (stockUnits). * * @return the list of rolled in StockUnits */ List<StockUnit> rollInMissingStockUnits(String dossierIdentifier, Collection<Position> positions, int destinationId) { List<UniqueUnit> uniqueUnits = new ArrayList<>(); StockUnitEao stockUnitEao = new StockUnitEao(stockEm); UniqueUnitEao uniqueUnitEao = new UniqueUnitEao(uniqueUnitEm); for (Position position : positions) { if ( stockUnitEao.findByUniqueUnitId(position.getUniqueUnitId()) == null ) { uniqueUnits.add(uniqueUnitEao.findById(position.getUniqueUnitId())); } } if ( uniqueUnits.isEmpty() ) return Collections.EMPTY_LIST; StockTransactionEmo transactionEmo = new StockTransactionEmo(stockEm); StockTransaction rollInTransaction = transactionEmo.requestRollInPrepared(destinationId, arranger, "RollIn durch Gutschrift " + dossierIdentifier); for (UniqueUnit uu : uniqueUnits) { StockUnit stockUnit = new StockUnit(uu.getIdentifier(UniqueUnit.Identifier.REFURBISHED_ID), uu.getProduct().getName(), uu.getId()); rollInTransaction.addUnit(stockUnit); stockEm.persist(stockUnit); } List<StockUnit> rolledInUnits = transactionEmo.completeRollIn(arranger, Arrays.asList(rollInTransaction)); L.info("Missing Units rolled In: {}", toIds(rolledInUnits)); return rolledInUnits; } /** * Validate if all StockUnits are on the destination Stock, and if not transfer them. * * @param stockUnits the stockUnits to validate */ void optionalTransferToDestination(List<StockUnit> stockUnits, int destinationId) { List<StockUnit> onWrongStock = new ArrayList<>(); for (StockUnit stockUnit : stockUnits) { if ( stockUnit.isInTransaction() ) continue; // We are ignoring everything on a transaction. if ( stockUnit.getStock().getId() != destinationId ) onWrongStock.add(stockUnit); } if ( onWrongStock.isEmpty() ) return; StockTransactionEmo transactionEmo = new StockTransactionEmo(stockEm); StockTransaction transfer = transactionEmo.requestExternalTransferPrepare(onWrongStock.get(0).getStock().getId(), destinationId, arranger, "Transfer durch Gutschrift"); for (StockUnit stockUnit : onWrongStock) transfer.addUnit(stockUnit); List<StockUnit> transferdUnits = transactionEmo.completeExternalTransfer(arranger, Arrays.asList(transfer)); L.info("Destination {} for units on wrong stock used: {}", transferdUnits); } /** * Map the Positions of the altered Document by Contractors of the referencing UniqueUnits * * @return the mapped association. */ Map<TradeName, List<Position>> mapPositionsToContrator(Collection<Position> positions) { Map<TradeName, List<Position>> result = new HashMap<>(); UniqueUnitEao uniqueUnitEao = new UniqueUnitEao(uniqueUnitEm); for (Position pos : positions) { TradeName contractor = uniqueUnitEao.findById(pos.getUniqueUnitId()).getContractor(); if ( !result.containsKey(contractor) ) result.put(contractor, new ArrayList<>()); result.get(contractor).add(pos.partialClone()); } return result; } /** * Create the Mirror Dossier on the SystemCustomer. * * @param customerId the customerId of the SystemCusotmer * @return the created Dossier */ Dossier createMirrorDossier(long customerId) { return createWorkflow.execute(customerId, false, arranger); } void updateMirrorPositions(String dossierIdentifier, Document mirror, Collection<Position> positions) { mirror.appendAll(positions); mirror.append(new PositionBuilder() .setBookingAccount(postLedger.get(PositionType.COMMENT).orElse(-1)) .setType(PositionType.COMMENT) .setName("Gutschrift von Dossier " + dossierIdentifier) .setDescription("Gutschrift von Dossier " + dossierIdentifier + " durch " + arranger) .createPosition()); } /** * Executes the Workflow. * * @param alteredDocument * @param destinationId * @param aranger * @return the updated Document. */ public Document execute(Document alteredDocument, Integer destinationId, String aranger) { // TODO: don't do this in stateless, works for now. this.arranger = aranger; Document previousDoc = new DocumentEao(redTapeEm).findById(alteredDocument.getId(), LockModeType.PESSIMISTIC_WRITE); validate(alteredDocument, previousDoc, destinationId); if ( alteredDocument.equalsContent(previousDoc) ) return alteredDocument; L.info("Workflow on {} by {}", DocumentFormater.toSimpleLine(alteredDocument), arranger); Document newDocument = refreshAndPrepare(alteredDocument, previousDoc); if ( alteredDocument.getType() != previousDoc.getType() ) { // A Complaint is now complete. if ( previousDoc.getType() == DocumentType.COMPLAINT ) previousDoc.setDirective(Document.Directive.NONE); generateIdentifier(newDocument); } if ( !alteredDocument.isStillExactlyBriefed(previousDoc) ) newDocument.remove(Document.Flag.CUSTOMER_EXACTLY_BRIEFED); newDocument.setHistory(new DocumentHistory(arranger, "Update durch " + getClass().getSimpleName())); redTapeEm.persist(newDocument); validateAfter(newDocument.getDossier()); if ( alteredDocument.getType() == previousDoc.getType() ) return newDocument; // Is this a first time change if ( !alteredDocument.containsPositionType(PositionType.UNIT) ) return newDocument; // Is it a full CreditMemo on a Unit List<StockUnit> stockUnits = optionalRemoveFromLogicTransaction(newDocument); List<StockUnit> stockUnits2 = rollInMissingStockUnits(newDocument.getDossier().getIdentifier(), newDocument.getPositions(PositionType.UNIT).values(), destinationId); optionalTransferToDestination(stockUnits, destinationId); Map<TradeName, List<Position>> contractorPositions = mapPositionsToContrator(newDocument.getPositions(PositionType.UNIT).values()); for (TradeName contractor : contractorPositions.keySet()) { Document mirror = createMirrorDossier(repaymentCustomers.get(contractor).get()).getActiveDocuments().get(0); updateMirrorPositions(newDocument.getDossier().getIdentifier(), mirror, contractorPositions.get(contractor)); equilibrateLogicTransaction(mirror); } return newDocument; } private List<String> toIds(List<StockUnit> result) { List<String> units = new ArrayList<>(); for (StockUnit stockUnit : result) { units.add("StockUnit(id=" + stockUnit.getId() + ",refurbishedId=" + stockUnit.getRefurbishId() + ")"); } return units; } }