/* * 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; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; import javax.ejb.Stateless; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.persistence.EntityManager; import org.slf4j.*; import eu.ggnet.dwoss.common.log.AutoLogger; import eu.ggnet.dwoss.configuration.GlobalConfig; import eu.ggnet.dwoss.customer.api.CustomerService; import eu.ggnet.dwoss.customer.api.UiCustomer; import eu.ggnet.dwoss.mandator.api.value.PostLedger; import eu.ggnet.dwoss.redtape.api.LegacyBridge; import eu.ggnet.dwoss.redtape.api.RedTapeHookService; import eu.ggnet.dwoss.redtape.assist.RedTapes; import eu.ggnet.dwoss.redtape.eao.DossierEao; import eu.ggnet.dwoss.redtape.eao.PositionEao; import eu.ggnet.dwoss.redtape.entity.Dossier; import eu.ggnet.dwoss.redtape.entity.Position; import eu.ggnet.dwoss.redtape.format.DossierFormater; import eu.ggnet.dwoss.report.assist.Reports; import eu.ggnet.dwoss.report.eao.ReportLineEao; import eu.ggnet.dwoss.report.entity.Report; import eu.ggnet.dwoss.report.entity.ReportLine; import eu.ggnet.dwoss.rights.api.AtomicRight; import eu.ggnet.dwoss.rights.eao.OperatorEao; import eu.ggnet.dwoss.rights.entity.Operator; import eu.ggnet.dwoss.rules.PositionType; import eu.ggnet.dwoss.stock.assist.Stocks; import eu.ggnet.dwoss.stock.eao.StockUnitEao; import eu.ggnet.dwoss.stock.emo.LogicTransactionEmo; import eu.ggnet.dwoss.stock.entity.LogicTransaction; import eu.ggnet.dwoss.stock.entity.StockUnit; import eu.ggnet.dwoss.stock.format.StockUnitFormater; import eu.ggnet.dwoss.uniqueunit.api.UnitShard; import eu.ggnet.dwoss.uniqueunit.assist.UniqueUnits; import eu.ggnet.dwoss.uniqueunit.eao.UniqueUnitEao; import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit; import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit.Identifier; import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnitHistory; import eu.ggnet.dwoss.uniqueunit.format.UniqueUnitFormater; import eu.ggnet.dwoss.util.*; import eu.ggnet.dwoss.util.interactiveresult.Result; import static eu.ggnet.dwoss.report.entity.ReportLine.SingleReferenceType.WARRANTY; import static eu.ggnet.dwoss.rules.PositionType.PRODUCT_BATCH; import static eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit.Identifier.REFURBISHED_ID; import static java.util.Locale.GERMANY; /** * A EJB to supply Information about Units backed up by multiple data sources. * <p/> * @author oliver.guenther */ @Stateless @AutoLogger public class UnitOverseerBean implements UnitOverseer { private final Logger L = LoggerFactory.getLogger(UnitOverseerBean.class); @Inject @Stocks private EntityManager stockEm; @Inject @UniqueUnits private EntityManager uuEm; @Inject @RedTapes private EntityManager redTapeEm; @Inject @Reports private EntityManager reportEm; @Inject private CustomerService customerService; @Inject private Instance<LegacyBridge> bridgeInstance; @Inject private Instance<RedTapeHookService> redTapeHook; @Inject private PostLedger postLedger; @Inject private OperatorEao operatorEao; private boolean hasRight(String username, AtomicRight right) { if ( username == null ) return false; Operator operator = operatorEao.findByUsername(username); if ( operator == null ) return false; return operator.getAllActiveRights().contains(AtomicRight.VIEW_COST_AND_REFERENCE_PRICES); } /** * Find a Unit and its representative and return a html formated String representing it. * Ensure to add the html start/end tags manually * <p/> * @param uniqueUnitId the uniqueUnitId * @param username * @return a html formated String representing a Unit. */ @Override public String toDetailedHtml(int uniqueUnitId, String username) { UniqueUnitEao uuEao = new UniqueUnitEao(uuEm); UniqueUnit uniqueUnit = uuEao.findById(uniqueUnitId); if ( uniqueUnit != null ) return toDetailedHtmlUnit(uniqueUnit, hasRight(username, AtomicRight.VIEW_COST_AND_REFERENCE_PRICES)); // Unique Unit is null, optional fallback to legacy system. return "<h1>Keine Informationen zu UniqueUnitId " + uniqueUnitId + "</h1>"; } /** * Find a Unit and its representative and return a html formated String representing it. * Ensure to add the html start/end tags manually * <p/> * @param refurbishId the refurbishedId or serial * @param username * @return a html formated String representing a Unit. */ @Override public String toDetailedHtml(String refurbishId, String username) { UniqueUnitEao uuEao = new UniqueUnitEao(uuEm); UniqueUnit uniqueUnit = uuEao.findByIdentifier(Identifier.REFURBISHED_ID, refurbishId); // Try Serail if Sopo does not match. if ( uniqueUnit == null ) uniqueUnit = uuEao.findByIdentifier(Identifier.SERIAL, refurbishId); if ( uniqueUnit != null ) return toDetailedHtmlUnit(uniqueUnit, hasRight(username, AtomicRight.VIEW_COST_AND_REFERENCE_PRICES)); // Unique Unit is null, optional fallback to legacy system. if ( !bridgeInstance.isUnsatisfied() && !bridgeInstance.get().isUnitIdentifierAvailable(refurbishId) ) return "<i><u>Informationen aus Legacy System Sopo:</u></i>" + bridgeInstance.get().toDetailedHtmlUnit(refurbishId); return "<h1>Keine Informationen zu SopoNr/Seriennummer " + refurbishId + "</h1>"; } private String toDetailedHtmlUnit(UniqueUnit uniqueUnit, boolean showPrices) { SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyy"); StockUnit stockUnit = new StockUnitEao(stockEm).findByUniqueUnitId(uniqueUnit.getId()); List<ReportLine> reportLines = new ReportLineEao(reportEm).findByUniqueUnitId(uniqueUnit.getId()); String re = UniqueUnitFormater.toHtmlDetailed(uniqueUnit); TreeSet<Dossier> dossiers = new TreeSet<>(Dossier.ORDER_INVERSE_ACTIVE_ACTUAL); for (Position pos : new PositionEao(redTapeEm).findByUniqueUnitId(uniqueUnit.getId())) { if ( !pos.getDocument().isActive() ) continue; // For now we ignore all Dossiers which just had the unit in the history dossiers.add(pos.getDocument().getDossier()); } re += "<hr />"; re += "<b>Vorgänge:</b><ul>"; if ( dossiers.isEmpty() ) re += "<li>Keine Vorgänge vorhanden</li>"; for (Dossier dossier : dossiers) { re += "<li>"; re += customerService.asUiCustomer(dossier.getCustomerId()).toNameCompanyLine(); re += DossierFormater.toHtmlSimpleWithDocument(dossier) + "<br /></li>"; } re += "</ul>"; re += "<hr />"; if ( uniqueUnit.getHistory() != null && !uniqueUnit.getHistory().isEmpty() ) { re += "<b>Unit History:</b><ul>"; for (UniqueUnitHistory history : new TreeSet<>(uniqueUnit.getHistory())) { re += "<li>" + df.format(history.getOccurence()) + " - " + history.getComment() + "</li>"; } re += "</ul>"; } re += "<hr />"; re += "<p><b>Lagerinformationen</b><br />"; if ( stockUnit == null ) re += "Kein Lagergerät vorhanden<br />"; else re += StockUnitFormater.toHtml(stockUnit); re += "</p>"; re += "<hr />"; re += "<b>Reporting-Informationen</b>"; if ( reportLines == null || reportLines.isEmpty() ) re += "Keine Reporting-Informationen vorhanden<br />"; else { re += "<table border=\"1\"><tr>"; re += wrap("<th>", "</th>", "Id", "ReportDate", "Kid", "SopoNr", "Type", "Dossier", "Report"); re += "</tr>"; for (ReportLine l : reportLines) { re += "<tr>"; re += wrap("<td>", "</td>", l.getId(), DateFormats.ISO.format(l.getReportingDate()), l.getCustomerId(), l.getRefurbishId(), l.getPositionType() == PRODUCT_BATCH && l.getReference(WARRANTY) != null ? "Garantieerweiterung" : l.getPositionType().getName(), l.getDossierIdentifier() + ", " + l.getDocumentType().getName() + l.getWorkflowStatus().getSign() + (l.getDocumentIdentifier() == null ? "" : ", " + l.getDocumentIdentifier()), l.getReports().stream().map(Report::getName).collect(Collectors.joining(",")) ); } re += "</table><br />"; } if ( !showPrices ) return re; re += "<hr />"; re += "<b>Preis-Informationen</b>"; NumberFormat nf = NumberFormat.getCurrencyInstance(GERMANY); re += "<ul><li>Unit Preise:"; re += uniqueUnit.getPrices().entrySet().stream() .map(e -> e.getKey() + " : " + nf.format(e.getValue())) .collect(Collectors.joining("</li><li>", "<ul><li>", "</li></ul>")); re += "</li><li>Produkt Preise:"; re += uniqueUnit.getProduct().getPrices().entrySet().stream() .map(e -> e.getKey() + " : " + nf.format(e.getValue())) .collect(Collectors.joining("</li><li>", "<ul><li>", "</li></ul>")); re += "</li>"; return re; } private static String wrap(String head, String foot, Object... elmes) { StringBuilder sb = new StringBuilder(); for (Object elme : elmes) { sb.append(head).append(elme).append(foot); } return sb.toString(); } /** * Find an available StockUnit and locks it by add to a LogicTransaction via DossierId. * <p/> * If no unit is found a LayerEightException is thrown. * <p/> * @param dossierId The Dossiers ID * @param refurbishedId The refurbished id for the Unique Unit search * @throws IllegalStateException if the refurbishId is not available */ @Override public void lockStockUnit(long dossierId, String refurbishedId) throws IllegalStateException { if ( !internalFind(refurbishedId).isAvailable() ) throw new IllegalStateException("Trying to lock refusbishId " + refurbishedId + ", but it is not available!"); UniqueUnit uu = new UniqueUnitEao(uuEm).findByIdentifier(Identifier.REFURBISHED_ID, refurbishedId); StockUnit stockUnit = new StockUnitEao(stockEm).findByUniqueUnitId(uu.getId()); LogicTransaction lt = new LogicTransactionEmo(stockEm).request(dossierId); lt.add(stockUnit); } /** * Find a Unit by its refurbished id and returns it. * <p/> * This method will throw a UserInfoException describing, why the unit is not available. * <p/> * @param refurbishId The refurbished id of the UniqueUnit * @param documentId * @return a Unit by its refurbished id or null if nothing is found of the unit is not available. * @throws UserInfoException if the refurbishId is not available */ @Override public Result<List<Position>> createUnitPosition(String refurbishId, long documentId) throws UserInfoException { UnitShard us = internalFind(refurbishId); if ( !us.isAvailable() ) throwNotAvailable(refurbishId, us); UniqueUnit uu = new UniqueUnitEao(uuEm).findByIdentifier(Identifier.REFURBISHED_ID, refurbishId); Position p = Position .builder() .amount(1) .price(0.) .tax(GlobalConfig.TAX) .afterTaxPrice(MathUtil.roundedApply(0., GlobalConfig.TAX, 0.)) .serialNumber(uu.getSerial()) .refurbishedId(uu.getRefurbishId()) .bookingAccount(postLedger.get(PositionType.UNIT).orElse(-1)) .type(PositionType.UNIT) .tax(GlobalConfig.TAX) .uniqueUnitId(uu.getId()) .uniqueUnitProductId(uu.getProduct().getId()) .name(UniqueUnitFormater.toPositionName(uu)) .description(UniqueUnitFormater.toDetailedDiscriptionLine(uu)) .build(); if ( redTapeHook.isUnsatisfied() ) return new Result(Arrays.asList(p)); //return Result return redTapeHook.get().elaborateUnitPosition(p, documentId); } /** * Build and throw an exception for a not available unit. * <p> * @param refurbishId the refurbished id of the unit * @param us the unit shard * @throws UserInfoException */ private void throwNotAvailable(String refurbishId, UnitShard us) throws UserInfoException { if ( us.getAvailable() == null ) throw new UserInfoException("SopoNr " + refurbishId + " existiert nicht"); // <- auch in di auslagerung... StockUnit stockUnit = new StockUnitEao(stockEm).findByUniqueUnitId(us.getUniqueUnitId()); if ( stockUnit != null && stockUnit.getLogicTransaction() != null ) { Dossier dos = new DossierEao(redTapeEm).findById(stockUnit.getLogicTransaction().getDossierId()); if ( dos == null ) throw new UserInfoException("SopoNr " + refurbishId + " is on a LogicTransaction, but there is no Dossier, inform Team Software"); UiCustomer customer = customerService.asUiCustomer(dos.getCustomerId()); if ( customer == null ) throw new UserInfoException("SopoNr " + refurbishId + " is on Dossier " + dos.getIdentifier() + ", but Customer " + dos.getCustomerId() + " does not exist."); throw new UserInfoException("SopoNr " + refurbishId + " ist schon vergeben" + "\nKID = " + customer.getId() + "\nKunde = " + customer.toTitleNameLine() + "\n\nVorgang = " + dos.getIdentifier()); } } /** * Returns true if the unit identified by the refurbishId is available for sale, else false. * <p/> * @param refurbishId the id to check * @return true if the unit identified by the refurbishId is available for sale, else false. */ @Override public boolean isAvailable(String refurbishId) { return internalFind(refurbishId).isAvailable(); } /** * Returns a UnitShard, a small representation of the refurbishId and its status. * <p/> * @param refurbishId the refurbishId to check. * @return a UnitShard, a small representation of the refurbishId and its status. */ @Override public UnitShard find(String refurbishId) { return internalFind(refurbishId); } private UnitShard internalFind(String refurbishId) { L.debug("find({})", refurbishId); UniqueUnitEao uuEao = new UniqueUnitEao(uuEm); UniqueUnit uu = uuEao.findByIdentifier(Identifier.REFURBISHED_ID, refurbishId); String oldRefurbishedOd = null; L.debug("find({}) uniqueUnit={}", refurbishId, uu); if ( uu == null ) { uu = uuEao.findByRefurbishedIdInHistory(refurbishId); if ( uu == null ) { if ( !bridgeInstance.isUnsatisfied() && !bridgeInstance.get().isUnitIdentifierAvailable(refurbishId) ) { return new UnitShard(refurbishId, 0, toHtmlDescription(refurbishId, null, "Nicht Verfügbar", "", "(Auskunft aus Sopo)"), false, null); } else { return new UnitShard(refurbishId, 0, "<html>SopoNr.:<b>" + refurbishId + "<u> existiert nicht.</u><br /><br /></b></html>", null, null); } } else { oldRefurbishedOd = "(Frühere SopoNr: " + refurbishId + ")"; refurbishId = uu.getIdentifier(REFURBISHED_ID); } } StockUnit stockUnit = new StockUnitEao(stockEm).findByUniqueUnitId(uu.getId()); L.debug("find({}) stockUnit={}", refurbishId, stockUnit); Integer stockId = null; if ( stockUnit != null && stockUnit.isInStock() ) stockId = stockUnit.getStock().getId(); if ( stockUnit == null ) { return new UnitShard(refurbishId, uu.getId(), toHtmlDescription(refurbishId, oldRefurbishedOd, "Nicht Verfügbar", "", null), false, null); } if ( stockUnit.getLogicTransaction() != null ) { return new UnitShard(refurbishId, uu.getId(), toHtmlDescription(refurbishId, oldRefurbishedOd, "Nicht Verfügbar", stockUnit, null), false, stockId); } // If the Database is clean, the Unit is available, but we make some safty checks here. if ( new DossierEao(redTapeEm).isUnitBlocked(uu.getId()) ) { L.warn("find({}) Database Error RedTape sanity check", refurbishId); return new UnitShard(refurbishId, uu.getId(), toHtmlDescription(refurbishId, oldRefurbishedOd, "Nicht Verfügbar", stockUnit, "(Datenbankfehler, RedTape sanity check!)"), false, stockId); } // Now we are shure. return new UnitShard(refurbishId, uu.getId(), toHtmlDescription(refurbishId, oldRefurbishedOd, "Verfügbar", stockUnit, null), true, stockId); } private String toHtmlDescription(String refurbishId, String oldRefurbishedId, String status, StockUnit stockUnit, String error) { String stockInfo = ""; if ( stockUnit.isInTransaction() ) stockInfo = "Transaction(" + stockUnit.getTransaction().getId() + "," + stockUnit.getTransaction().getType() + ")" + (stockUnit.getTransaction().getSource() == null ? "" : " von " + stockUnit.getTransaction().getSource().getName()) + (stockUnit.getTransaction().getDestination() == null ? "" : " nach " + stockUnit.getTransaction().getDestination().getName()); else if ( stockUnit.isInStock() ) stockInfo = stockUnit.getStock().getName(); return toHtmlDescription(refurbishId, oldRefurbishedId, status, stockInfo, error); } private String toHtmlDescription(String refurbishId, String oldRefurbishedId, String status, String stockInfo, String error) { String result = "<html>" + "<table border =\"0\" width=\"100%\">" + "<tr>" + "<td width=\"90px\">SopoNr.: <b>" + refurbishId + "</b></td>" + "<td align=\"right\" width=\"105px\"><b>" + status + "</b></td>" + "</tr>"; if ( error != null ) { result += "<tr>" + "<td width=\"180px\" colspan=\"2\"><i>" + error + "</i></td>" + "</tr>"; } else if ( oldRefurbishedId != null ) { result += "<tr>" + "<td width=\"180px\" colspan=\"2\"><i>" + oldRefurbishedId + "</i></td>" + "</tr>"; } result += "<tr>" + "<td width=\"180px\" colspan=\"2\">" + stockInfo + "</td>" + "</tr>" + "</table>" + "</html>"; return result; } }