/*
* 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.reporting;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.ejb.*;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.validation.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.*;
import eu.ggnet.dwoss.customer.api.CustomerService;
import eu.ggnet.dwoss.customer.api.UiCustomer;
import eu.ggnet.dwoss.event.UnitHistory;
import eu.ggnet.dwoss.mandator.api.service.WarrantyService;
import eu.ggnet.dwoss.mandator.api.value.ReceiptCustomers;
import eu.ggnet.dwoss.progress.MonitorFactory;
import eu.ggnet.dwoss.progress.SubMonitor;
import eu.ggnet.dwoss.redtape.assist.RedTapes;
import eu.ggnet.dwoss.redtape.eao.DossierEao;
import eu.ggnet.dwoss.redtape.entity.Document.Condition;
import eu.ggnet.dwoss.redtape.entity.Document.Directive;
import eu.ggnet.dwoss.redtape.entity.*;
import eu.ggnet.dwoss.redtape.workflow.RedTapeWorkflow;
import eu.ggnet.dwoss.report.assist.Reports;
import eu.ggnet.dwoss.report.eao.ReportLineEao;
import eu.ggnet.dwoss.report.entity.ReportLine;
import eu.ggnet.dwoss.rules.DocumentType;
import eu.ggnet.dwoss.rules.PositionType;
import eu.ggnet.dwoss.stock.assist.Stocks;
import eu.ggnet.dwoss.stock.eao.*;
import eu.ggnet.dwoss.stock.emo.StockTransactionEmo;
import eu.ggnet.dwoss.stock.entity.*;
import eu.ggnet.dwoss.uniqueunit.assist.UniqueUnits;
import eu.ggnet.dwoss.uniqueunit.eao.ProductEao;
import eu.ggnet.dwoss.uniqueunit.eao.UniqueUnitEao;
import eu.ggnet.dwoss.uniqueunit.entity.Product;
import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit;
import eu.ggnet.dwoss.util.DateFormats;
import eu.ggnet.saft.api.progress.IMonitor;
import eu.ggnet.statemachine.State.Type;
import static eu.ggnet.dwoss.redtape.entity.Document.Condition.*;
import static eu.ggnet.dwoss.report.entity.ReportLine.SingleReferenceType.WARRANTY;
import static eu.ggnet.dwoss.rules.DocumentType.BLOCK;
import static eu.ggnet.dwoss.rules.PaymentMethod.*;
import static eu.ggnet.dwoss.rules.PositionType.COMMENT;
import static eu.ggnet.dwoss.rules.PositionType.UNIT;
import static eu.ggnet.dwoss.uniqueunit.entity.PriceType.*;
import static org.apache.commons.lang3.StringUtils.*;
/**
* Operation for closing of RedTape.
*
* @author oliver.guenther
*/
@Singleton
public class RedTapeCloserOperation implements RedTapeCloser {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private final static Logger L = LoggerFactory.getLogger(RedTapeCloserOperation.class);
@Inject
@RedTapes
private EntityManager redTapeEm;
@Inject
private LogicTransactionEao ltEao;
@Inject
private StockEao stockEao;
@Inject
private StockUnitEao suEao;
@Inject
private StockTransactionEmo stEmo;
@Inject
@UniqueUnits
private EntityManager uuEm;
@Inject
@Reports
private EntityManager reportEm;
@Inject
private MonitorFactory monitorFactory;
@Inject
private Event<UnitHistory> history;
@Inject
private CustomerService customerService;
@Inject
private Instance<WarrantyService> warrantyServiceInstance;
@Inject
private ReceiptCustomers receiptCustomers;
/**
* Executes the closing manual.
* See {@link #closeing(java.lang.String, boolean) } for details.
* <p>
* @param arranger the arranger
*/
@Override
public void executeManual(String arranger) {
closeing(arranger, true);
}
/**
* Exectues the closing automatic.
* See {@link #closeing(java.lang.String, boolean) } for details.
* <p>
*/
@Schedule(hour = "22") // This kicks in exaclty once a day
@Override
public void executeAutomatic() {
closeing("scheduler (automatic)", false);
}
/**
* The closing selects and closes all dossiers and documents in a closable state. This includes production of appropriated values for report.
* <p/>
* The workflow:
* <ol>
* <li>Load all open {@link Dossiers}, and filter them by the closing states of the associated {@link Documents}.See
* {@link #findReportable(de.dw.progress.IMonitor)}</li>
* <li>Filter out all Document, which have {@link StockUnit}'s on open {@link StockTransaction}.</li>
* <li>Create the resulting {@link ReportLine}'s and persist them in the report database. See
* {@link #poluteReporting(java.util.Set, java.util.Date, de.dw.progress.IMonitor)} </li>
* <li>Close the selected {@link Dossier}'s and {@link Document}'s in redTape. See {@link #closeRedTape(java.util.Set, de.dw.progress.IMonitor)}</li>
* <li>Find all associated {@link StockUnit}'s and roll them out. See
* {@link #closeStock(java.util.Set, java.lang.String, java.lang.String, de.dw.progress.IMonitor)}</li>
* </ol>
*
* <p/>
* @param arranger the arranger
* @param manual is this called manual or automatic
*/
private void closeing(String arranger, boolean manual) {
Date now = new Date();
String msg = (manual ? "Manueller" : "Automatischer") + " (Tages)abschluss vom " + DateFormats.ISO.format(now) + " ausgeführt durch " + arranger;
L.info("closing:{}", msg);
SubMonitor m = monitorFactory.newSubMonitor((manual ? "Manueller" : "Automatischer") + " (Tages)abschluss", 100);
m.start();
L.info("closing:week persisted");
Set<Document> reportable = findReportable(m.newChild(30));
L.info("closing:documents selected");
reportable = filterOpenStockTransactions(reportable, m.newChild(5));
L.info("closing:documents filtered");
poluteReporting(reportable, DateUtils.truncate(new Date(), Calendar.DATE), m.newChild(15));
L.info("closing:repoting poluted");
closeRedTape(reportable, m.newChild(10));
L.info("closed:redTape:reportables");
closeRedTape(findCloseableBlocker(), m.newChild(10));
L.info("closed:redTape:nonreportables");
closeStock(reportable.stream()
.map(Document::getDossier)
.map(Dossier::getId)
.collect(Collectors.toSet()),
"Rollout by " + (manual ? "manuel" : "automatic") + " closing on " + DateFormats.ISO.format(now), arranger, m);
L.info("closed:stock");
m.finish();
}
/**
* Closes the Documents and it's dossiers. This simply sets {@link Document#closed} and {@link Dossier#closed} to true.
* For information on reopening {@link Dossier}'s see {@link RedTapeWorkflow#refreshAndPrepare(de.dw.redtape.entity.Document, de.dw.redtape.entity.Document)
* }.
* <p/>
* @param documents the documents to close
* @param monitor a optional monitor.
* @return a container instance with dossierIds to be closed and/or reopened in sopo.
*/
private void closeRedTape(Set<Document> documents, IMonitor monitor) {
SubMonitor m = SubMonitor.convert(monitor, documents.size());
m.start();
for (Document document : documents) {
m.worked(1, " verbuche " + document.getIdentifier());
document.setClosed(true);
document.getDossier().setClosed(true);
}
m.finish();
}
/**
* Rolling out the units from the stocks. For this, a {@link StockTransaction} with {@link StockTransactionType#ROLL_OUT} is created,
* all {@link StockUnit}<code>s</code> which are on a {@link LogicTransaction} with matching dossierId are added to this {@link StockTransaction} and
* it gets {@link StockTransactionStatusType#COMPLETED}.
* <p/>
* @param dossierIds the dossierId as reference.
* @param msg a msg for the stocktransaction.
* @param arranger the arranger.
* @param monitor a optional monitor.
* @return the amount of rolled out units.
*/
private int closeStock(Set<Long> dossierIds, String msg, String arranger, IMonitor monitor) {
SubMonitor m = SubMonitor.convert(monitor, 100);
final String h = "Stock:";
m.message(h + "lade logische Transaktionen");
// Rolling out
List<LogicTransaction> lts = ltEao.findByDossierIds(dossierIds);
m.worked(3, h + "sortiere Geräte nach Lager");
stockEao.findAll();
Map<Stock, List<StockUnit>> unitsByStock = lts.stream().flatMap((t) -> t.getUnits().stream()).collect(Collectors.groupingBy(StockUnit::getStock));
validateStockUnits(unitsByStock);
m.setWorkRemaining((int)unitsByStock.values().stream().count());
List<StockTransaction> stockTransactions = new ArrayList<>();
for (Entry<Stock, List<StockUnit>> entry : unitsByStock.entrySet()) {
StockTransaction st = stEmo.requestRollOutPrepared(entry.getKey().getId(), arranger, msg);
for (StockUnit stockUnit : entry.getValue()) {
m.worked(1, h + "verbuche (refurbishId=" + stockUnit.getRefurbishId() + ",uniqueUnitId=" + stockUnit.getUniqueUnitId() + ")");
st.addUnit(stockUnit);
history.fire(new UnitHistory(stockUnit.getUniqueUnitId(), msg, arranger));
}
stockTransactions.add(st);
}
m.message(h + "auslagern");
if ( !stockTransactions.isEmpty() ) stEmo.completeRollOut(arranger, stockTransactions);
m.finish();
return (int)unitsByStock.values().stream().count();
}
private void validateStockUnits(Map<Stock, List<StockUnit>> unitsByStock) {
// Duplicated Validation, safetynet.
if ( unitsByStock.containsKey(null) ) throw new RuntimeException("StockUnits have no Stock, on Transaction ? : " + unitsByStock.get(null));
unitsByStock.values().stream().flatMap(l -> l.stream()).forEach((StockUnit u) -> {
if ( !validator.validate(u).isEmpty() ) throw new ConstraintViolationException("Invalid StockUnit " + u,
new HashSet<>(validator.validate(u)));
});
}
/**
* Filters out all {@link Document}'s with associated {@link StockUnit}'s on an open {@link StockTransaction}.
* See {@link StockTransactionStatusType} and {@link StockTransaction#POSSIBLE_STATUS_TYPES} for details about open {@link StockTransaction}.
* <p>
* <p/>
* @param documents the documents as reference
* @param monitor a optional monitor
* @return all documents which are not on open transactions.
*/
private Set<Document> filterOpenStockTransactions(Set<Document> documents, IMonitor monitor) {
SubMonitor m = SubMonitor.convert(monitor, documents.size());
m.start();
m.message(" filtere");
for (Iterator<Document> it = documents.iterator(); it.hasNext();) {
Document document = it.next();
m.worked(1, " filtere " + document.getIdentifier());
LogicTransaction lt = ltEao.findByDossierId(document.getDossier().getId());
if ( lt == null ) continue;
for (StockUnit stockUnit : lt.getUnits()) {
if ( !validator.validate(stockUnit).isEmpty() || stockUnit.isInTransaction() || stockUnit.getStock() == null ) {
it.remove();
L.warn("Closing: The Dossier(id={},customerId={}) has the Unit(refurbhisId={})"
+ " which is in an invalid state (validation error,open StockTransaction), excluding Dossier from closing.",
document.getDossier().getId(), document.getDossier().getCustomerId(), stockUnit.getRefurbishId());
break;
}
}
}
m.finish();
return documents;
}
/**
* Discovers all Documents, which are in closing state.
* <p>
* Closing States are:
* <table border="1" >
* <thead>
* <tr><th>Case</th><th>Document Type</th><th>PaymentMethod</th><th>Conditions</th><th>Directive</th></tr>
* </thead>
* <tbody>
* <tr>
* <td>1</td>
* <td>{@link Type#ORDER}</td>
* <td>*</td>
* <td>{@link Condition#CANCELED}</td>
* <td>*</td>
* </tr>
* <tr>
* <td>2</td>
* <td>{@link Type#INVOICE} overwrites {@link Type#ORDER}</td>
* <td>{@link PaymentMethod#ADVANCE_PAYMENT}</td>
* <td>{@link Condition#PAID} & ( {@link Condition#SENT} | {@link Condition#PICKED_UP} )</td>
* <td>*</td>
* </tr>
* <tr>
* <td>3</td>
* <td>{@link Type#INVOICE} overwrites {@link Type#ORDER}</td>
* <td>{@link PaymentMethod#CASH_ON_DELIVERY}</td>
* <td>{@link Condition#SENT}</td>
* <td>*</td>
* </tr>
* <tr>
* <td>4</td>
* <td>{@link Type#INVOICE} overwrites {@link Type#ORDER}</td>
* <td>{@link PaymentMethod#DIRECT_DEBIT}</td>
* <td>({@link Condition#PAID} & ( {@link Condition#SENT} | {@link Condition#PICKED_UP} ))</td>
* <td>*</td>
* </tr>
* <tr>
* <td>5</td>
* <td>{@link Type#INVOICE} overwrites {@link Type#ORDER}</td>
* <td>{@link PaymentMethod#INVOICE}</td>
* <td>{@link Condition#SENT} | {@link Condition#PICKED_UP}</td>
* <td>*</td>
* </tr>
* <tr>
* <td>6</td>
* <td>{@link Type#ANNULATION_INVOICE} | {@link Type#CREDIT_MEMO}</td>
* <td>*</td>
* <td>*</td>
* <td>*</td>
* </tr>
* <tr>
* <td>7</td>
* <td>{@link Type#COMPLAINT}</td>
* <td>*</td>
* <td>*</td>
* <td>*</td>
* </tr>
* <tr>
* <td>8</td>
* <td>{@link Type#CAPITAL_ASSET} | {@link Type#RETURNS}</td>
* <td>*</td>
* <td>{@link Condition#PICKED_UP}</td>
* <td>{@link Directive#NONE}</td>
* </tr>
* </tbody>
* </table>
* Comments:
* <ul>
* <li>A canceled order will be closed, but not reported.</li>
* <li>A complaint is a very special case, which might be closed two times. See {@link RedTapeWorkflow#refreshAndPrepare(de.dw.redtape.entity.Document, de.dw.redtape.entity.Document)
* }</li>
* </ul>
* <p>
* @param monitor a optional monitor.
* @return all documents, which are in a closing state.
*/
private Set<Document> findReportable(IMonitor monitor) {
SubMonitor m = SubMonitor.convert(monitor);
m.start();
m.message(" lade offene Vorgänge");
List<Dossier> openDossiers = new DossierEao(redTapeEm).findByClosed(false);
m.worked(5);
m.setWorkRemaining(openDossiers.size());
Set<Document> closeable = new HashSet<>();
for (Dossier dossier : openDossiers) {
m.worked(1, " selecting " + dossier.getIdentifier());
// Check if there is only an order.
if ( dossier.getActiveDocuments().size() == 1 && dossier.getActiveDocuments(DocumentType.ORDER).size() == 1 ) {
Document doc = dossier.getActiveDocuments(DocumentType.ORDER).get(0);
if ( doc.getConditions().contains(CANCELED) ) closeable.add(doc);
L.debug("Filtered not reportable {}, cause: canceled order", doc.getDossier().getIdentifier());
// Shortcut: If there is only an order, that is not canceled. we do not close it. If it is canceled, we close it.
continue;
}
// Check the Closing State. Every closable document is removed from the copied collection.
// If the collection is empty at the end, the dossier can be closed, meaning we remove all cases that we consider closing state.
List<Document> activeDocuments = new ArrayList<>(dossier.getActiveDocuments());
for (Iterator<Document> it = activeDocuments.iterator(); it.hasNext();) {
Document document = it.next();
Set<Condition> conditions = document.getConditions();
if ( document.isClosed() ) {
it.remove();
// At this point a ORDER is never the only Document, therfore we safly ignore it.
// All Repayments get reported on creation.
} else if ( document.getType() == DocumentType.ORDER || document.getType() == DocumentType.ANNULATION_INVOICE || document.getType() == DocumentType.CREDIT_MEMO ) {
it.remove();
} else if ( document.getType() == DocumentType.INVOICE ) {
switch (dossier.getPaymentMethod()) {
case ADVANCE_PAYMENT:
if ( conditions.contains(PAID) && (conditions.contains(SENT) || conditions.contains(PICKED_UP)) ) it.remove();
break;
case CASH_ON_DELIVERY:
if ( conditions.contains(SENT) ) it.remove();
break;
case DIRECT_DEBIT:
if ( conditions.contains(PAID) && (conditions.contains(SENT) || conditions.contains(PICKED_UP)) ) it.remove();
break;
case INVOICE:
if ( conditions.contains(SENT) || conditions.contains(PICKED_UP) ) it.remove();
break;
}
} else if ( document.getType() == DocumentType.CAPITAL_ASSET || document.getType() == DocumentType.RETURNS ) {
if ( conditions.contains(PICKED_UP) ) it.remove();
} else if ( document.getType() == DocumentType.COMPLAINT ) {
// A Complaint gets allways closed. See RedTapeWorkflow.refreshAndPrepare() for the reopening conditions.
it.remove();
}
// TODO: There might be a special case, that someone made the CreditMemo on the Invoice, but had a Complait before.
// We should cleanup this also. See: http://overload.ahrensburg.gg-net.de/jira/browse/DW-831
}
// Empty means, all documents in a dossier are either closed or in a closing state, expect Blocks and orders.
if ( activeDocuments.isEmpty() ) {
for (Document document : dossier.getActiveDocuments()) {
if ( document.getType() == DocumentType.ORDER ) continue; // The Order part is never Reported.
if ( document.getType() == DocumentType.BLOCK ) continue; // Should never happen, no concept jet.
if ( document.isClosed() ) continue; // Don't close it twice
closeable.add(document);
}
} else if ( L.isDebugEnabled() ) {
List<String> shorts = new ArrayList<>();
String identifier = activeDocuments.get(0).getIdentifier();
for (Document document : activeDocuments) {
shorts.add(document.toTypeConditions());
}
L.debug("Filtered not reportable {}, cause: Not closeable documents: {}", identifier, shorts);
}
}
m.finish();
return closeable;
}
private Set<Document> findCloseableBlocker() {
Collection<Long> receipts = receiptCustomers.getReceiptCustomers().values();
List<Dossier> openDossiers = new DossierEao(redTapeEm).findByClosed(false)
.stream().filter(d -> !receipts.contains(d.getCustomerId())).collect(Collectors.toList());
//all active blockers from open dossiers
Set<Document> blocker = openDossiers.stream()
.filter(d -> !d.getActiveDocuments(BLOCK).isEmpty())
.map(d -> d.getActiveDocuments(BLOCK))
.flatMap(Collection::stream)
.collect(Collectors.toSet());
//directly closable, only comment positions
Set<Document> closable = blocker.stream()
.filter(d -> d.getPositions().values().stream().allMatch(p -> p.getType() == COMMENT))
.collect(Collectors.toSet());
//documents containing at least one unit type position
Set<Document> containsUnit = blocker.stream()
.filter(d -> d.getPositions().values().stream().anyMatch(p -> p.getType() == UNIT))
.collect(Collectors.toSet());
//remove all documents where at least one unit is still in stock
containsUnit.removeIf(
d -> d.getPositions().values().stream().anyMatch(p -> p.getType() == UNIT && suEao.findByUniqueUnitId(p.getUniqueUnitId()) != null));
closable.addAll(containsUnit);
return closable;
}
/**
* Actually creating reportLines from the reportable documents.
* For each position of a {@link Document} a {@link ReportLine} is created with all information supplied.
* <p>
* Exceptions are:
* <ul>
* <li>A {@link Document} with a {@link Condition#CANCELED} which is silently ignored</li>
* <li>A {@link Document} with a {@link DocumentType#COMPLAINT} which sets all prices on the {@link ReportLine} to 0 and
* <ul>
* <li>If the {@link Document} has the {@link Condition#WITHDRAWN} or {@link Condition#REJECTED}, set {@link ReportLine#workflowStatus} to
* {@link ReportLine.WorkflowStatus#DISCHARGED}</li>
* <li>If the {@link Document} has the {@link Condition#ACCEPTED}, set {@link ReportLine#workflowStatus} to
* {@link ReportLine.WorkflowStatus#CHARGED}</li>
* <li>Otherwise set {@link ReportLine#workflowStatus} to {@link ReportLine.WorkflowStatus#UNDER_PROGRESS}</li>
* </ul>
* </li>
* <li>A {@link Document} with a {@link DocumentType#CREDIT_MEMO} gets its prices inverted</li>
* </ul>
* <p/>
* @param reportable the documents to create lines from
* @param monitor a optional monitor.
* @return the amount of created lines.
*/
private int poluteReporting(Set<Document> reportable, Date reporting, IMonitor monitor) {
WarrantyService warrantyService = null;
if ( !warrantyServiceInstance.isUnsatisfied() ) {
warrantyService = warrantyServiceInstance.get();
}
SubMonitor m = SubMonitor.convert(monitor, reportable.size() + 10);
m.start();
ReportLineEao reportLineEao = new ReportLineEao(reportEm);
UniqueUnitEao uniqueUnitEao = new UniqueUnitEao(uuEm);
ProductEao productEao = new ProductEao(uuEm);
int amountCreate = 0;
List<ReportLine> newLines = new ArrayList<>(reportable.size());
for (Document document : reportable) {
m.worked(1, "reported " + document.getIdentifier());
// A canceled document must be closed, but must not create a reportline.
if ( document.getConditions().contains(Condition.CANCELED) ) continue;
ReportLine l;
for (Position position : document.getPositions().values()) {
amountCreate++;
l = new ReportLine();
l.setActual(document.getActual());
l.setAmount(position.getAmount());
l.setBookingAccount(position.getBookingAccount());
l.setCustomerId(document.getDossier().getCustomerId());
l.setDescription(normalizeSpace(position.getDescription()));
l.setDocumentId(document.getId());
l.setDocumentIdentifier(document.getIdentifier());
l.setDocumentType(document.getType());
l.setDossierId(document.getDossier().getId());
l.setDossierIdentifier(document.getDossier().getIdentifier());
// TODO: We could use something else for a separator, but keep in mind that we want to avoid name, , , something.
l.setInvoiceAddress(normalizeSpace(document.getInvoiceAddress().getDescription()));
l.setName(normalizeSpace(position.getName()));
l.setPositionType(position.getType());
l.setPrice(position.getPrice());
l.setAfterTaxPrice(position.getAfterTaxPrice());
l.setReportingDate(reporting);
l.setTax(position.getTax());
l.setMarginPercentage(0); // Set via Report afterwards
l.setPurchasePrice(0); // Set via Report afterwards
UiCustomer c = customerService.asUiCustomer(document.getDossier().getCustomerId());
if ( c != null ) {
l.setCustomerCompany(c.getCompany());
l.setCustomerName(c.toTitleNameLine());
l.setCustomerEmail(c.getEmail());
}
// A Credit Memo gets its prices inverted
if ( document.getType() == DocumentType.CREDIT_MEMO ) {
l.setPrice(position.getPrice() * (-1));
l.setAfterTaxPrice(position.getAfterTaxPrice() * (-1));
}
// Special handling of complaints.
if ( document.getType() == DocumentType.COMPLAINT ) {
// A Complaint position has "tagging" effect, but shall never result in a plus or minus.
l.setPrice(0);
l.setAfterTaxPrice(0);
if ( document.getConditions().contains(Condition.REJECTED) || document.getConditions().contains(Condition.WITHDRAWN) ) {
l.setWorkflowStatus(ReportLine.WorkflowStatus.DISCHARGED);
} else if ( document.getConditions().contains(Condition.ACCEPTED) ) {
l.setWorkflowStatus(ReportLine.WorkflowStatus.CHARGED);
} else {
l.setWorkflowStatus(ReportLine.WorkflowStatus.UNDER_PROGRESS);
}
}
// Extra information for Type Position
if ( position.getType() == PositionType.UNIT || position.getType() == PositionType.UNIT_ANNEX ) {
UniqueUnit uu = Objects.requireNonNull(uniqueUnitEao.findById(position.getUniqueUnitId()),
"No UniqueUnit with id=" + position.getUniqueUnitId());
Product p = uu.getProduct();
if ( uu.getContractor() == p.getTradeName().getManufacturer() ) {
l.setContractorPartNo(p.getPartNo());
l.setContractorReferencePrice(p.getPrice(MANUFACTURER_COST));
} else {
l.setContractorPartNo(p.getAdditionalPartNo(uu.getContractor()));
l.setContractorReferencePrice(p.getPrice(CONTRACTOR_REFERENCE));
}
l.setManufacturerCostPrice(p.getPrice(MANUFACTURER_COST));
l.setContractor(uu.getContractor());
l.setContractorReferencePrice(p.getPrice(CONTRACTOR_REFERENCE));
if ( Math.abs(l.getContractorReferencePrice()) < 0.001 ) l.setContractorReferencePrice(p.getPrice(MANUFACTURER_COST));
l.setMfgDate(uu.getMfgDate());
l.setRefurbishId(uu.getRefurbishId());
l.setSerial(uu.getSerial());
l.setUniqueUnitId(uu.getId());
l.setSalesChannel(uu.getSalesChannel());
l.setPartNo(p.getPartNo());
l.setProductBrand(p.getTradeName());
l.setProductName(p.getName());
l.setProductGroup(p.getGroup());
l.setProductId(p.getId());
// Extra Information for Type Product Batch
} else if ( position.getType() == PositionType.PRODUCT_BATCH ) {
Product p = Objects.requireNonNull(productEao.findById(position.getUniqueUnitProductId()),
"No Product for id=" + position.getUniqueUnitProductId());
l.setPartNo(p.getPartNo());
l.setProductBrand(p.getTradeName());
l.setProductGroup(p.getGroup());
l.setProductName(p.getName());
l.setProductId(p.getId());
l.setUniqueUnitId(position.getUniqueUnitId());
l.setSerial(position.getSerial());
l.setRefurbishId(position.getRefurbishedId());
if ( warrantyService != null ) {
l.setContractor(warrantyService.warrantyContractor(p.getPartNo())); // If this is no warranty Partno, this will return null ;-)
}
}
reportEm.persist(l);
newLines.add(l);
}
}
reportEm.flush();
m.message("Updateing References");
for (ReportLine newLine : newLines) {
if ( newLine.getUniqueUnitId() < 1 ) continue; // Not Refs for Product_Batches or Versandkosten jet.
if ( newLine.getPositionType() == PositionType.PRODUCT_BATCH ) {
// TODO: also evaluate the productId.
newLine.addAll(reportLineEao.findBySerialAndPositionTypeAndDossierId(newLine.getSerial(), newLine.getPositionType(), newLine.getDossierId()));
} else {
newLine.addAll(reportLineEao.findUnitsAlike(newLine.getUniqueUnitId(), newLine.getDossierId()));
}
}
updateSingleReferences(newLines);
m.worked(5);
m.finish();
return amountCreate;
}
/**
* Sets the single References.
* <p>
* @param lines
*/
void updateSingleReferences(Collection<ReportLine> lines) {
if ( warrantyServiceInstance.isUnsatisfied() ) return;
for (ReportLine line : lines) {
if ( !warrantyServiceInstance.get().isWarranty(line.getPartNo()) ) continue;
for (ReportLine unit : lines) {
if ( unit.getPositionType() != UNIT ) continue;
if ( line.getDocumentId() == unit.getDocumentId()
&& line.getWorkflowStatus() == unit.getWorkflowStatus()
&& StringUtils.equals(line.getRefurbishId(), unit.getRefurbishId()) ) {
unit.setReference(WARRANTY, line);
}
}
}
}
}