/*
* 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.redtape.entity.Address;
import eu.ggnet.dwoss.redtape.entity.Dossier;
import eu.ggnet.dwoss.redtape.entity.RedTapeCounter;
import eu.ggnet.dwoss.redtape.entity.DocumentHistory;
import eu.ggnet.dwoss.redtape.entity.Document;
import java.util.ArrayList;
import java.util.Date;
import javax.persistence.EntityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.ggnet.dwoss.mandator.api.value.Mandator;
import eu.ggnet.dwoss.mandator.api.value.partial.DocumentIdentifierGeneratorConfiguration;
import eu.ggnet.dwoss.redtape.eao.AddressEao;
import eu.ggnet.dwoss.redtape.emo.RedTapeCounterEmo;
import eu.ggnet.dwoss.redtape.entity.util.DocumentEquals;
import eu.ggnet.dwoss.rules.DocumentType;
import eu.ggnet.dwoss.rules.PositionType;
import eu.ggnet.dwoss.stock.emo.EquilibrationResult;
import eu.ggnet.dwoss.stock.emo.LogicTransactionEmo;
import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import static eu.ggnet.dwoss.redtape.entity.util.DocumentEquals.Property.*;
/**
* Workflow Superclass.
* It is mearly a form of Util class but allows to be used extended.
*
* @author oliver.guenther
*/
@RequiredArgsConstructor
@AllArgsConstructor
public abstract class RedTapeWorkflow {
protected final Logger L = LoggerFactory.getLogger(this.getClass());
protected final EntityManager redTapeEm;
protected final EntityManager uniqueUnitEm;
protected final EntityManager stockEm;
// TODO: push down.
protected String arranger;
protected final Mandator mandator;
public Document execute() {
throw new UnsupportedOperationException("Not Yet Implemented");
}
protected void removeLogicTransaction(Document document) {
equilibrateOrRemoveLogicTransaction(document, true);
}
protected void equilibrateLogicTransaction(Document document) {
equilibrateOrRemoveLogicTransaction(document, false);
}
/**
* Equilibrates the LogicTransaction to the Document and added History to all UniqueUnits.
* <p>
* @param document
* @param remove if true everything is removed.
* @return
*/
protected void equilibrateOrRemoveLogicTransaction(Document document, boolean remove) {
LogicTransactionEmo ltEmo = new LogicTransactionEmo(stockEm);
EquilibrationResult equilibrate = ltEmo.equilibrate(document.getDossier().getId(), remove ? new ArrayList<>() : document.getPositionsUniqueUnitIds());
if ( equilibrate == null ) return;
L.debug("Equilibrated Stock LogicTransaction: {}", equilibrate);
for (Integer uuid : equilibrate.getAdded()) {
uniqueUnitEm.find(UniqueUnit.class, uuid).addHistory(
"Added to Dossier " + document.getDossier().getIdentifier() + " of Customer " + document.getDossier().getCustomerId() + " by " + arranger);
}
for (Integer uuid : equilibrate.getRemoved()) {
uniqueUnitEm.find(UniqueUnit.class, uuid).addHistory(
"Removed from Dossier " + document.getDossier().getIdentifier() + " of Customer " + document.getDossier().getCustomerId() + " by " + arranger);
}
}
/**
* Clones the new Document from the altered Document, sets all detached Entities and updates the active status.
* <p>
* @param alteredDetachedDocument
* @param previousDocument
* @return
*/
protected Document refreshAndPrepare(Document alteredDetachedDocument, Document previousDocument) {
//Partiall Clone our next Document
Document newDocument = alteredDetachedDocument.partialClone();
// Replace detached entities
AddressEao addEao = new AddressEao(redTapeEm);
Address invoiceAddress = addEao.findById(alteredDetachedDocument.getInvoiceAddress().getId());
Address shippingAddress = addEao.findById(alteredDetachedDocument.getShippingAddress().getId());
newDocument.setInvoiceAddress(invoiceAddress);
newDocument.setShippingAddress(shippingAddress);
newDocument.setHistory(new DocumentHistory(arranger, "Update durch Workflow"));
// Set automatic Information
newDocument.setDossier(previousDocument.getDossier());
newDocument.setPredecessor(previousDocument);
newDocument.setActive(true);
if ( previousDocument.getType() == newDocument.getType() ) {
previousDocument.setActive(false);
// A Complaint gets reopend on condition change.
if ( newDocument.getType() == DocumentType.COMPLAINT && !previousDocument.getConditions().equals(newDocument.getConditions()) ) {
newDocument.setClosed(false);
newDocument.getDossier().setClosed(false);
}
} else {// On Document Type Change, the dossier gets reopened and some cleanup is happening.
newDocument.getDossier().setClosed(false);
newDocument.setClosed(false);
newDocument.setIdentifier(null);
newDocument.setActual(new Date());
newDocument.remove(Document.Flag.CUSTOMER_BRIEFED);
newDocument.remove(Document.Flag.CUSTOMER_EXACTLY_BRIEFED);
}
L.debug("Prepared {}", newDocument);
return newDocument;
}
/**
* Validates afterwards: Check if still needed.
*
* @param dossier
*/
protected void validateAfter(Dossier dossier) {
// TODO: Check if still needed.
Long oneOrder = null;
Long oneInvoice = null;
for (Document d : dossier.getActiveDocuments()) {
switch (d.getType()) {
case ORDER:
if ( oneOrder != null ) throw new RuntimeException("More than one Order Acitve: " + d.getId() + " and " + oneOrder + ", " + dossier);
else oneOrder = d.getId();
break;
case INVOICE:
if ( oneInvoice != null ) throw new RuntimeException("More than one Invoice Acitve: " + d.getId() + " and " + oneInvoice + ", " + dossier);
else oneInvoice = d.getId();
break;
default:
}
}
}
/**
* Optionally generates and sets the identifier for the document.
* The Identifier is only generated if the mandator has a configuration for the document type.
* <p>
* @param document
* @return
*/
protected String generateIdentifier(Document document) {
// Verify if a config exist.
DocumentIdentifierGeneratorConfiguration digc = mandator.getDocumentIdentifierGeneratorConfigurations().get(document.getType());
if ( digc == null ) return null;
RedTapeCounter counter = new RedTapeCounterEmo(redTapeEm).requestNext(document.getType(), digc.getPrefixType().generate());
String identifier = digc
.getPattern()
.replace(DocumentIdentifierGeneratorConfiguration.VAR_PREFIX, counter.getPrefix())
.replace(DocumentIdentifierGeneratorConfiguration.VAR_COUNTER, digc.getCounterFormat().format(counter.getValue()));
document.setIdentifier(identifier);
L.debug("Generated Identifier: {}", document.getIdentifier());
return identifier;
}
protected void validate(Document altered, Document previous) {
// Check that the in db is correct
Dossier dos = previous.getDossier();
if ( altered.getOptLock() != previous.getOptLock() )
throw new IllegalStateException("The Previous Document has a different optLock, so it was changed since the last read. Please try cancel, refresh the dossier and that the change.");
if ( previous.getConditions().contains(Document.Condition.CANCELED) ) throw new IllegalStateException(
"The Previous Document is canceled.\nAltered: " + altered + "\nPrevious: " + previous);
for (DocumentType type : DocumentType.values()) {
if ( type == DocumentType.CREDIT_MEMO
|| type == DocumentType.COMPLAINT
|| type == DocumentType.ANNULATION_INVOICE ) continue;
if ( dos.getActiveDocuments(type).size() > 1 ) throw new IllegalStateException(
"The Dossier(id=" + dos.getId() + ") has more than one active Document of Type " + type
+ "\n" + dos + "\n" + dos.getActiveDocuments(type));
}
if ( !previous.isActive() ) throw new IllegalStateException("The Previous Document is not active.\nAltered: " + altered + "\nPrevious: " + previous);
// A Change of Type may occour on closed documents
// TODO: Check, that the Type Change is allowed (e.g. from RETURNS to CAPITAL_ASSET obviously not
if ( altered.getType() != previous.getType() ) return;
if ( !previous.isClosed() ) return;
// A Complaint is the only Document, that may be reported twice, so a reopening is possible.
if ( previous.getType() == DocumentType.COMPLAINT ) return;
// Now to the restrictive handling of closed documents.
if ( !dos.changesAllowed(altered.getDossier()) ) throw new IllegalStateException("The Dossier is clossed and supplied changes are not allowed."
+ "\nAltered: " + altered.getDossier() + "\nPrevious: " + dos);
DocumentEquals documentEquals = new DocumentEquals()
.ignore(ID, ACTIVE, HISTORY, PREDECESSOR, DIRECTIVE, CONDITIONS, FLAGS, SETTLEMENTS)
.ignoreAddresses()
.igonrePositionOrder()
.ignorePositions(PositionType.COMMENT);
if ( !documentEquals.equals(previous, altered) ) throw new IllegalStateException("The Document is clossed and supplied changes are not allowed."
+ documentEquals.equalsMessage(previous, altered));
}
}