/*
* 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.gen;
import eu.ggnet.dwoss.rules.PositionType;
import eu.ggnet.dwoss.rules.DocumentType;
import eu.ggnet.dwoss.customer.api.CustomerMetaData;
import eu.ggnet.dwoss.customer.api.CustomerService;
import eu.ggnet.dwoss.progress.SubMonitor;
import eu.ggnet.dwoss.progress.MonitorFactory;
import eu.ggnet.dwoss.redtape.entity.Dossier;
import eu.ggnet.dwoss.redtape.entity.Position;
import eu.ggnet.dwoss.redtape.entity.Document;
import eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit;
import eu.ggnet.dwoss.uniqueunit.entity.Product;
import eu.ggnet.dwoss.uniqueunit.entity.PriceType;
import java.util.*;
import javax.ejb.*;
import javax.inject.Inject;
import eu.ggnet.dwoss.configuration.GlobalConfig;
import eu.ggnet.dwoss.mandator.api.value.PostLedger;
import eu.ggnet.dwoss.redtape.RedTapeWorker;
import eu.ggnet.statemachine.StateTransition;
import eu.ggnet.dwoss.stock.StockAgent;
import eu.ggnet.dwoss.stock.entity.StockUnit;
import eu.ggnet.dwoss.uniqueunit.UniqueUnitAgent;
import eu.ggnet.dwoss.uniqueunit.format.UniqueUnitFormater;
import eu.ggnet.dwoss.util.MathUtil;
import eu.ggnet.dwoss.util.validation.ValidationUtil;
import eu.ggnet.dwoss.redtape.state.CustomerDocument;
import eu.ggnet.dwoss.redtape.state.RedTapeStateTransition;
import static eu.ggnet.dwoss.rules.CustomerFlag.SYSTEM_CUSTOMER;
import static eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit.Identifier.REFURBISHED_ID;
import static java.util.stream.Collectors.toList;
/**
*
* @aimport static eu.ggnet.dwoss.uniqueunit.entity.UniqueUnit.Identifier.REFURBISHED_ID;
* uthor oliver.guenther
*/
@Stateless
public class RedTapeGeneratorOperation {
private final static Random R = new Random();
@EJB
private RedTapeWorker redTapeWorker;
@EJB
private UniqueUnitAgent uniqueUnitAgent;
@EJB
private StockAgent stockAgent;
@Inject
private MonitorFactory monitorFactory;
@Inject
private CustomerService customerService;
@Inject
private PostLedger postLedger;
/**
* Generates a random amount of dossiers in a random valid state using already persisted elements like available units and product batches.
* <p/>
* @param amount
* @return the list of generated dossiers.
*/
// TODO: Some usefull repayments would be nice.
public List<Dossier> makeSalesDossiers(int amount) {
SubMonitor m = monitorFactory.newSubMonitor("Erzeuge " + amount + " Dossiers", amount);
m.start();
if ( amount < 1 ) return Collections.EMPTY_LIST;
List<CustomerMetaData> customers = customerService.allAsCustomerMetaData()
.stream().filter(c -> !c.getFlags().contains(SYSTEM_CUSTOMER)).collect(toList());
if ( customers.isEmpty() ) throw new RuntimeException("No Customers found, obviously there are non in the database");
List<UniqueUnit> freeUniqueUnits = uniqueUnitAgent.findAllEager(UniqueUnit.class);
List<Product> products = uniqueUnitAgent.findAllEager(Product.class);
List<Dossier> dossiers = new ArrayList<>();
for (int i = 0; i <= amount; i++) {
CustomerMetaData customer = customers.get(R.nextInt(customers.size()));
// Create a dossier on a random customer.
Dossier dos = redTapeWorker.create(customer.getId(), R.nextBoolean(), "Generated by RedTapeGeneratorOperation.makeSalesDossiers()");
Document doc = dos.getActiveDocuments(DocumentType.ORDER).get(0);
int noOfPositions = R.nextInt(10) + 2; // At least two positions.
Set<Long> productIds = new HashSet<>();
for (int j = 0; j < noOfPositions; j++) {
// Add Some units, but make sure, not only units are added.
if ( j < (noOfPositions - 2) && !freeUniqueUnits.isEmpty() ) {
UniqueUnit uu = null;
while (uu == null && !freeUniqueUnits.isEmpty()) {
uu = freeUniqueUnits.remove(0);
StockUnit su = stockAgent.findStockUnitByUniqueUnitIdEager(uu.getId());
if ( su == null || su.getLogicTransaction() != null ) uu = null; // Saftynet, so no unit is set double.
}
if ( uu == null ) continue;
double price = uu.getPrice(PriceType.CUSTOMER);
if ( price < 0.001 ) price = uu.getPrice(PriceType.RETAILER);
if ( price < 0.001 ) price = 1111.11;
Position pos = Position.builder()
.amount(1)
.type(PositionType.UNIT)
.uniqueUnitId(uu.getId())
.uniqueUnitProductId(uu.getProduct().getId())
.price(price)
.tax(GlobalConfig.TAX)
.afterTaxPrice(MathUtil.roundedApply(price, GlobalConfig.TAX, 0.))
.description(UniqueUnitFormater.toDetailedDiscriptionLine(uu))
.name(UniqueUnitFormater.toPositionName(uu))
.bookingAccount(-1)
.refurbishedId(uu.getIdentifier(REFURBISHED_ID))
.build();
doc.append(pos);
continue;
}
double price = (R.nextInt(100000) + 100) / 100;
switch (R.nextInt(3)) { // Add a random position
case 0: // Add a Product Batch
Product p;
int k = 0;
do {
p = products.get(R.nextInt(products.size()));
k++;
if ( k > 10 )
throw new RuntimeException("Could find a alternative product : p.size=" + products.size() + ", pids.size=" + productIds.size());
} while (productIds.contains(p.getId()));
productIds.add(p.getId());
doc.append(Position.builder()
.amount(R.nextInt(10) + 1)
.type(PositionType.PRODUCT_BATCH)
.uniqueUnitProductId(p.getId())
.price(price)
.tax(GlobalConfig.TAX)
.afterTaxPrice(MathUtil.roundedApply(price, GlobalConfig.TAX, 0.))
.name(p.getName())
.description(p.getDescription())
.bookingAccount(postLedger.get(PositionType.PRODUCT_BATCH).orElse(-1))
.build());
break;
case 1: // Add a Service
doc.append(Position.builder()
.amount((R.nextInt(100) + 1) / 4.0)
.type(PositionType.SERVICE)
.price(price)
.tax(GlobalConfig.TAX)
.afterTaxPrice(MathUtil.roundedApply(price, GlobalConfig.TAX, 0.))
.name("Service")
.description("Service")
.bookingAccount(postLedger.get(PositionType.SERVICE).orElse(-1))
.build());
break;
case 2: // Add a comment
doc.append(Position.builder()
.amount(1)
.type(PositionType.COMMENT)
.name("Comment")
.description("Comment")
.bookingAccount(postLedger.get(PositionType.COMMENT).orElse(-1))
.build());
break;
}
}
if ( dos.isDispatch() ) { // add the shipping costs.
double price = (R.nextInt(10) + 1) * 10;
doc.append(Position.builder()
.amount(1)
.type(PositionType.SHIPPING_COST)
.price(price)
.tax(GlobalConfig.TAX)
.afterTaxPrice(MathUtil.roundedApply(price, GlobalConfig.TAX, 0.))
.name("Versandkosten")
.description("Versandkosten")
.bookingAccount(postLedger.get(PositionType.SHIPPING_COST).orElse(-1))
.build());
}
// Break, if what we build is wrong.
ValidationUtil.validate(doc);
doc = redTapeWorker.update(doc, null, "JUnit");
for (int j = 0; j <= R.nextInt(4); j++) {
CustomerDocument cd = new CustomerDocument(customer.getFlags(), doc, customer.getShippingCondition(), customer.getPaymentMethod());
List<StateTransition<CustomerDocument>> transitions = redTapeWorker.getPossibleTransitions(cd);
if ( transitions.isEmpty() ) break;
RedTapeStateTransition transition = (RedTapeStateTransition)transitions.get(R.nextInt(transitions.size()));
if ( transition.getHints().contains(RedTapeStateTransition.Hint.CREATES_ANNULATION_INVOICE)
|| transition.getHints().contains(RedTapeStateTransition.Hint.CREATES_CREDIT_MEMO) ) break;
doc = redTapeWorker.stateChange(cd, transition, "JUnit");
}
dossiers.add(doc.getDossier());
m.worked(1, doc.getDossier().getIdentifier());
}
m.finish();
return dossiers;
}
}