/**
* ***************************************************************************
* Copyright (c) 2010 Qcadoo Limited
* Project: Qcadoo MES
* Version: 1.4
*
* This file is part of Qcadoo.
*
* Qcadoo is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
* ***************************************************************************
*/
package com.qcadoo.mes.materialFlow;
import static com.qcadoo.mes.basic.constants.BasicConstants.MODEL_PRODUCT;
import static com.qcadoo.mes.basic.constants.ProductFields.UNIT;
import static com.qcadoo.mes.materialFlow.constants.LocationFields.EXTERNAL_NUMBER;
import static com.qcadoo.mes.materialFlow.constants.MaterialsInLocationFields.MATERIALS_IN_LOCATION_COMPONENTS;
import static com.qcadoo.mes.materialFlow.constants.MaterialsInLocationFields.MATERIAL_FLOW_FOR_DATE;
import static com.qcadoo.mes.materialFlow.constants.StockCorrectionFields.FOUND;
import static com.qcadoo.mes.materialFlow.constants.StockCorrectionFields.LOCATION;
import static com.qcadoo.mes.materialFlow.constants.StockCorrectionFields.PRODUCT;
import static com.qcadoo.mes.materialFlow.constants.StockCorrectionFields.SHOULD_BE;
import static com.qcadoo.mes.materialFlow.constants.StockCorrectionFields.STOCK_CORRECTION_DATE;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.LOCATION_FROM;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.LOCATION_TO;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.NUMBER;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.QUANTITY;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.TIME;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.TYPE;
import static com.qcadoo.mes.materialFlow.constants.TransferType.CONSUMPTION;
import static com.qcadoo.mes.materialFlow.constants.TransferType.PRODUCTION;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.collect.Sets;
import com.qcadoo.localization.api.utils.DateUtils;
import com.qcadoo.mes.basic.constants.BasicConstants;
import com.qcadoo.mes.basic.util.CurrencyService;
import com.qcadoo.mes.materialFlow.constants.LocationFields;
import com.qcadoo.mes.materialFlow.constants.MaterialFlowConstants;
import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.model.api.DataDefinitionService;
import com.qcadoo.model.api.Entity;
import com.qcadoo.model.api.NumberService;
import com.qcadoo.model.api.search.SearchOrders;
import com.qcadoo.model.api.search.SearchProjections;
import com.qcadoo.model.api.search.SearchRestrictions;
import com.qcadoo.model.api.search.SearchResult;
import com.qcadoo.view.api.ComponentState;
import com.qcadoo.view.api.ViewDefinitionState;
import com.qcadoo.view.api.components.FieldComponent;
import com.qcadoo.view.api.components.LookupComponent;
import com.qcadoo.view.api.utils.NumberGeneratorService;
@Service
public class MaterialFlowService {
private static final String L_FORM = "form";
private static final String L_ID = "id";
private static final String L_TRANS = "trans";
private static final String L_TRANS_PRODUCT = "trans.product";
private static final String L_TRANS_PRODUCT_ID = "trans.product.id";
private static final String L_TRANS_LOCATION_TO_ID = "trans.locationTo.id";
private static final String L_LOC = "loc";
private static final String L_LOC_PRODUCT = "loc.product";
private static final String L_LOC_PRODUCT_ID = "loc.product.id";
private static final String L_LOC_LOCATION_ID = "loc.location.id";
private static final String L_LOCATION_ID = "location.id";
private static final String L_LOCATION_FROM_ID = "locationFrom.id";
private static final String L_LOCATION_TO_ID = "locationTo.id";
private static final String L_PRODUCT_ID = "product.id";
@Autowired
private DataDefinitionService dataDefinitionService;
@Autowired
private NumberGeneratorService numberGeneratorService;
@Autowired
private CurrencyService currencyService;
@Autowired
private NumberService numberService;
public BigDecimal calculateShouldBeInLocation(final Long locationId, final Long productId, final Date forDate) {
BigDecimal countProductIn = BigDecimal.ZERO;
BigDecimal countProductOut = BigDecimal.ZERO;
BigDecimal countProduct = BigDecimal.ZERO;
Date lastCorrectionDate = null;
DataDefinition transferDataCorrection = dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER,
MaterialFlowConstants.MODEL_STOCK_CORRECTION);
DataDefinition transferTo = dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER,
MaterialFlowConstants.MODEL_TRANSFER);
DataDefinition transferFrom = dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER,
MaterialFlowConstants.MODEL_TRANSFER);
Entity resultDataCorrection = transferDataCorrection.find().add(SearchRestrictions.eq(L_LOCATION_ID, locationId))
.add(SearchRestrictions.eq(L_PRODUCT_ID, productId)).addOrder(SearchOrders.desc(STOCK_CORRECTION_DATE))
.setMaxResults(1).uniqueResult();
if (resultDataCorrection != null) {
lastCorrectionDate = (Date) resultDataCorrection.getField(STOCK_CORRECTION_DATE);
countProduct = (BigDecimal) resultDataCorrection.getField(FOUND);
}
SearchResult resultTo = null;
SearchResult resultFrom = null;
if (lastCorrectionDate == null) {
resultTo = transferTo.find().add(SearchRestrictions.eq(L_LOCATION_TO_ID, locationId))
.add(SearchRestrictions.eq(L_PRODUCT_ID, productId)).add(SearchRestrictions.le(TIME, forDate)).list();
resultFrom = transferFrom.find().add(SearchRestrictions.eq(L_LOCATION_FROM_ID, locationId))
.add(SearchRestrictions.eq(L_PRODUCT_ID, productId)).add(SearchRestrictions.le(TIME, forDate)).list();
} else {
resultTo = transferTo.find().add(SearchRestrictions.eq(L_LOCATION_TO_ID, locationId))
.add(SearchRestrictions.eq(L_PRODUCT_ID, productId)).add(SearchRestrictions.le(TIME, forDate))
.add(SearchRestrictions.gt(TIME, lastCorrectionDate)).list();
resultFrom = transferFrom.find().add(SearchRestrictions.eq(L_LOCATION_FROM_ID, locationId))
.add(SearchRestrictions.eq(L_PRODUCT_ID, productId)).add(SearchRestrictions.le(TIME, forDate))
.add(SearchRestrictions.gt(TIME, lastCorrectionDate)).list();
}
for (Entity e : resultTo.getEntities()) {
BigDecimal quantity = (BigDecimal) e.getField(QUANTITY);
countProductIn = countProductIn.add(quantity, numberService.getMathContext());
}
for (Entity e : resultFrom.getEntities()) {
BigDecimal quantity = (BigDecimal) e.getField(QUANTITY);
countProductOut = countProductOut.add(quantity, numberService.getMathContext());
}
if (lastCorrectionDate == null) {
countProductIn = countProductIn.subtract(countProductOut, numberService.getMathContext());
} else {
countProductIn = countProductIn.add(countProduct, numberService.getMathContext());
countProductIn = countProductIn.subtract(countProductOut, numberService.getMathContext());
}
if (countProductIn.compareTo(BigDecimal.ZERO) == -1) {
countProductIn = BigDecimal.ZERO;
}
return countProductIn;
}
public void refreshShouldBeInStockCorrectionDetails(final ViewDefinitionState state, final ComponentState componentState,
final String[] args) {
refreshShouldBeInStockCorrectionDetails(state);
}
public void refreshShouldBeInStockCorrectionDetails(final ViewDefinitionState state) {
FieldComponent location = (FieldComponent) state.getComponentByReference(LOCATION);
FieldComponent product = (FieldComponent) state.getComponentByReference(PRODUCT);
FieldComponent date = (FieldComponent) state.getComponentByReference(STOCK_CORRECTION_DATE);
FieldComponent should = (FieldComponent) state.getComponentByReference(SHOULD_BE);
if ((location != null) && (product != null) && (date != null) && (location.getFieldValue() != null)
&& (product.getFieldValue() != null) && !date.getFieldValue().toString().equals("")) {
Long locationNumber = (Long) location.getFieldValue();
Long productNumber = (Long) product.getFieldValue();
Date forDate = DateUtils.parseDate(date.getFieldValue());
BigDecimal shouldBe = calculateShouldBeInLocation(locationNumber, productNumber, forDate);
if (shouldBe == null || shouldBe == BigDecimal.ZERO) {
should.setFieldValue(BigDecimal.ZERO);
} else {
should.setFieldValue(shouldBe);
}
}
should.requestComponentUpdateState();
}
public void fillNumberFieldValue(final ViewDefinitionState view) {
if (view.getComponentByReference(NUMBER).getFieldValue() != null) {
return;
}
numberGeneratorService.generateAndInsertNumber(view, MaterialFlowConstants.PLUGIN_IDENTIFIER,
MaterialFlowConstants.MODEL_TRANSFORMATIONS, L_FORM, NUMBER);
}
public void generateNumberForTransfer(final ViewDefinitionState state, final ComponentState componentState,
final String[] args) {
generateNumberAfterSelectingProduct(state, componentState, MaterialFlowConstants.MODEL_TRANSFER);
}
public void generateNumberForStockCorrection(final ViewDefinitionState state, final ComponentState componentState,
final String[] args) {
generateNumberAfterSelectingProduct(state, componentState, MaterialFlowConstants.MODEL_STOCK_CORRECTION);
}
public void generateNumberAfterSelectingProduct(final ViewDefinitionState state, final ComponentState componentState,
final String model) {
if (!(componentState instanceof FieldComponent)) {
throw new IllegalStateException("component is not FieldComponentState");
}
FieldComponent number = (FieldComponent) state.getComponentByReference(NUMBER);
FieldComponent productState = (FieldComponent) componentState;
if (!numberGeneratorService.checkIfShouldInsertNumber(state, L_FORM, NUMBER)) {
return;
}
if (productState.getFieldValue() != null) {
Entity product = getProductById((Long) productState.getFieldValue());
number.setFieldValue(generateNumberFromProduct(product, model));
}
number.requestComponentUpdateState();
}
public String generateNumberFromProduct(final Entity product, final String model) {
String number = "";
if (product != null) {
String generatedNumber = generateNumber(MaterialFlowConstants.PLUGIN_IDENTIFIER, model, 3);
String prefix = product.getStringField(NUMBER);
number = prefix + "-" + generatedNumber;
Long parsedNumber = Long.parseLong(generatedNumber);
while (numberAlreadyExist(model, number)) {
parsedNumber++;
number = prefix + "-" + String.format("%03d", parsedNumber);
}
}
return number;
}
public void fillUnitFieldValues(final ViewDefinitionState view, final ComponentState componentState, final String[] args) {
fillUnitFieldValues(view);
}
public void fillUnitFieldValues(final ViewDefinitionState view) {
Long productId = (Long) view.getComponentByReference(PRODUCT).getFieldValue();
if (productId == null) {
return;
}
Entity product = dataDefinitionService.get(BasicConstants.PLUGIN_IDENTIFIER, MODEL_PRODUCT).get(productId);
FieldComponent unitField = null;
String unit = product.getStringField(UNIT);
for (String referenceName : Sets.newHashSet("quantityUNIT", "shouldBeUNIT", "foundUNIT")) {
unitField = (FieldComponent) view.getComponentByReference(referenceName);
if (unitField == null) {
continue;
}
unitField.setFieldValue(unit);
unitField.requestComponentUpdateState();
}
}
public void fillCurrencyFieldValues(final ViewDefinitionState view, final ComponentState componentState, final String[] args) {
fillCurrencyFieldValues(view);
}
public void fillCurrencyFieldValues(final ViewDefinitionState view) {
FieldComponent currencyField = null;
String currency = currencyService.getCurrencyAlphabeticCode();
for (String referenceName : Sets.newHashSet("priceCurrency")) {
currencyField = (FieldComponent) view.getComponentByReference(referenceName);
if (currencyField == null) {
continue;
}
currencyField.setFieldValue(currency);
currencyField.requestComponentUpdateState();
}
}
public Map<Entity, BigDecimal> calculateMaterialQuantitiesInLocation(final Entity materialsInLocation) {
List<Entity> materialsInLocationComponents = new ArrayList<Entity>(
materialsInLocation.getHasManyField(MATERIALS_IN_LOCATION_COMPONENTS));
Map<Entity, BigDecimal> reportData = new HashMap<Entity, BigDecimal>();
for (Entity materialsInLocationComponent : materialsInLocationComponents) {
Entity location = materialsInLocationComponent.getBelongsToField(LOCATION);
List<Entity> products = getProductsSeenInLocation(location.getStringField(NUMBER));
Date forDate = ((Date) materialsInLocation.getField(MATERIAL_FLOW_FOR_DATE));
for (Entity product : products) {
BigDecimal quantity = calculateShouldBeInLocation(location.getId(), product.getId(), forDate);
if (reportData.containsKey(product)) {
reportData.put(product, reportData.get(product).add(quantity, numberService.getMathContext()));
} else {
reportData.put(product, quantity);
}
}
}
return reportData;
}
public List<Entity> getProductsSeenInLocation(final String locationNumber) {
DataDefinition dataDefLocation = dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER,
MaterialFlowConstants.MODEL_LOCATION);
Long id = dataDefLocation.find().add(SearchRestrictions.eq(NUMBER, locationNumber)).uniqueResult().getId();
DataDefinition dataDefProduct = dataDefinitionService.get(BasicConstants.PLUGIN_IDENTIFIER, BasicConstants.MODEL_PRODUCT);
List<Entity> productsFromTransfers = dataDefProduct.find().createAlias(MaterialFlowConstants.MODEL_TRANSFER, L_TRANS)
.addOrder(SearchOrders.asc(L_TRANS_PRODUCT_ID))
.setProjection(SearchProjections.distinct(SearchProjections.field(L_TRANS_PRODUCT)))
.add(SearchRestrictions.eqField(L_TRANS_PRODUCT_ID, L_ID)).add(SearchRestrictions.eq(L_TRANS_LOCATION_TO_ID, id))
.list().getEntities();
List<Entity> productsFromStockCorrections = dataDefProduct.find()
.createAlias(MaterialFlowConstants.MODEL_STOCK_CORRECTION, L_LOC).addOrder(SearchOrders.asc(L_LOC_PRODUCT_ID))
.setProjection(SearchProjections.distinct(SearchProjections.field(L_LOC_PRODUCT)))
.add(SearchRestrictions.eqField(L_LOC_PRODUCT_ID, L_ID)).add(SearchRestrictions.eq(L_LOC_LOCATION_ID, id)).list()
.getEntities();
for (Entity product : productsFromStockCorrections) {
if (!productsFromTransfers.contains(product)) {
productsFromTransfers.add(product);
}
}
return productsFromTransfers;
}
public void disableLocationFieldForParticularTransferType(final ViewDefinitionState view,
final ComponentState componentState, final String[] args) {
disableLocationFieldForParticularTransferType(view);
}
public void disableLocationFieldForParticularTransferType(final ViewDefinitionState view) {
if (view.getComponentByReference(TYPE).getFieldValue() == null) {
return;
}
String type = view.getComponentByReference(TYPE).getFieldValue().toString();
FieldComponent locationTo = (FieldComponent) view.getComponentByReference(LOCATION_TO);
FieldComponent locationFrom = (FieldComponent) view.getComponentByReference(LOCATION_FROM);
if (CONSUMPTION.getStringValue().equals(type)) {
locationFrom.setEnabled(true);
locationFrom.setRequired(true);
locationTo.setRequired(false);
locationTo.setEnabled(false);
locationTo.setFieldValue("");
} else if (PRODUCTION.getStringValue().equals(type)) {
locationTo.setEnabled(true);
locationTo.setRequired(true);
locationFrom.setRequired(false);
locationFrom.setEnabled(false);
locationFrom.setFieldValue("");
} else {
locationTo.setEnabled(true);
locationTo.setRequired(true);
locationFrom.setRequired(true);
locationFrom.setEnabled(true);
}
locationTo.requestComponentUpdateState();
locationFrom.requestComponentUpdateState();
}
public void fillDefaultLocationToFieldInTransformations(final ViewDefinitionState view, final ComponentState componentState,
final String[] args) {
FieldComponent locationTo = (FieldComponent) view.getComponentByReference(LOCATION_TO);
if (locationTo.getFieldValue() == null) {
FieldComponent locationFrom = (FieldComponent) view.getComponentByReference(LOCATION_FROM);
locationTo.setFieldValue(locationFrom.getFieldValue());
}
locationTo.requestComponentUpdateState();
}
public boolean numberAlreadyExist(final String model, final String number) {
return dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER, model).find()
.add(SearchRestrictions.eq(NUMBER, number)).setMaxResults(1).uniqueResult() != null;
}
public List<Entity> getLocationsFromDB() {
return dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER, MaterialFlowConstants.MODEL_LOCATION).find()
.list().getEntities();
}
private Entity getProductById(final Long productId) {
if (productId == null) {
return null;
}
return dataDefinitionService.get(BasicConstants.PLUGIN_IDENTIFIER, BasicConstants.MODEL_PRODUCT).get(productId);
}
public Entity getLocationById(final Long locationId) {
if (locationId == null) {
return null;
}
return dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER, MaterialFlowConstants.MODEL_LOCATION).get(
locationId);
}
public Entity getLocationByName(final String name) {
if (name == null) {
return null;
}
return dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER, MaterialFlowConstants.MODEL_LOCATION).find()
.add(SearchRestrictions.eq(LocationFields.NAME, name)).setMaxResults(1).uniqueResult();
}
public Entity getStaffById(final Long staffId) {
if (staffId == null) {
return null;
}
return dataDefinitionService.get(BasicConstants.PLUGIN_IDENTIFIER, BasicConstants.MODEL_STAFF).get(staffId);
}
public boolean checkIfLocationHasExternalNumber(final Entity location) {
if ((location == null) || (location.getStringField(EXTERNAL_NUMBER) == null)) {
return false;
}
return true;
}
public void checkIfLocationHasExternalNumber(final ViewDefinitionState view, final String lookupName) {
LookupComponent locationLookup = (LookupComponent) view.getComponentByReference(lookupName);
Entity location = locationLookup.getEntity();
if (checkIfLocationHasExternalNumber(location)) {
locationLookup.addMessage("materialFlow.validate.global.error.locationHasExternalNumber",
ComponentState.MessageType.FAILURE);
}
}
public String generateNumber(final String plugin, final String entityName, int digtsNumber) {
DataDefinition dataDefinition = dataDefinitionService.get(plugin, entityName);
SearchResult results = dataDefinition.find().setMaxResults(1).addOrder(SearchOrders.desc("id")).list();
long longValue = 0;
if (results.getEntities().isEmpty()) {
longValue++;
} else {
longValue = results.getEntities().get(0).getId();
while (numberAlreadyExist(dataDefinition, longValue, digtsNumber)) {
longValue++;
}
}
return String.format("%0" + digtsNumber + "d", longValue);
}
private boolean numberAlreadyExist(final DataDefinition dataDefinition, final long longValue, final int digitsNumber) {
return dataDefinition.find().add(SearchRestrictions.eq("number", String.format("%0" + digitsNumber + "d", longValue)))
.setMaxResults(1).uniqueResult() != null;
}
}