/**
* ***************************************************************************
* 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.materialFlowResources.service;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.qcadoo.mes.basic.constants.ProductFields;
import com.qcadoo.mes.basic.constants.UnitConversionItemFieldsB;
import com.qcadoo.mes.materialFlow.constants.LocationFields;
import com.qcadoo.mes.materialFlowResources.constants.*;
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.SearchCriteriaBuilder;
import com.qcadoo.model.api.search.SearchOrders;
import com.qcadoo.model.api.search.SearchRestrictions;
import com.qcadoo.model.api.units.PossibleUnitConversions;
import com.qcadoo.model.api.units.UnitConversionService;
import com.qcadoo.model.api.validators.ErrorMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import static com.qcadoo.mes.materialFlow.constants.TransferFields.TIME;
import static com.qcadoo.mes.materialFlowResources.constants.ResourceFields.QUANTITY;
@Service
public class ResourceManagementServiceImpl implements ResourceManagementService {
private static final String _FIRST_NAME = "firstName";
private static final String L_LAST_NAME = "lastName";
@Autowired
private DataDefinitionService dataDefinitionService;
@Autowired
private NumberService numberService;
@Autowired
private UnitConversionService unitConversionService;
@Autowired
private PalletNumberDisposalService palletNumberDisposalService;
@Autowired
private ResourceStockService resourceStockService;
@Autowired
private ReservationsService reservationsService;
public ResourceManagementServiceImpl() {
}
public ResourceManagementServiceImpl(DataDefinitionService dataDefinitionService, NumberService numberService,
UnitConversionService unitConversionService) {
this.dataDefinitionService = dataDefinitionService;
this.numberService = numberService;
this.unitConversionService = unitConversionService;
}
@Override
@Transactional
public void createResourcesForReceiptDocuments(final Entity document) {
Entity warehouse = document.getBelongsToField(DocumentFields.LOCATION_TO);
Object date = document.getField(DocumentFields.TIME);
for (Entity position : document.getHasManyField(DocumentFields.POSITIONS)) {
createResource(document, warehouse, position, date);
}
}
private void setResourceAttributesFromPosition(final Entity resource, final Entity position) {
List<Entity> attributes = position.getHasManyField(PositionFields.ATRRIBUTE_VALUES);
for (Entity attribute : attributes) {
attribute.setField(AttributeValueFields.RESOURCE, resource);
}
resource.setField(ResourceFields.ATRRIBUTE_VALUES, attributes);
}
private void setPositionAttributesFromResource(final Entity position, final Entity resource) {
List<Entity> attributes = resource.getHasManyField(ResourceFields.ATRRIBUTE_VALUES);
List<Entity> newAttributes = Lists.newArrayList();
DataDefinition attributeDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER,
MaterialFlowResourcesConstants.MODEL_ATTRIBUTE_VALUE);
for (Entity attribute : attributes) {
List<Entity> newAttribute = attributeDD.copy(attribute.getId());
newAttribute.get(0).setField(AttributeValueFields.POSITION, position);
newAttribute.get(0).setField(AttributeValueFields.RESOURCE, null);
newAttributes.addAll(newAttribute);
}
position.setField(PositionFields.ATRRIBUTE_VALUES, newAttributes);
}
private void setResourceAttributesFromResource(final Entity resource, final Entity baseResource) {
List<Entity> attributes = baseResource.getHasManyField(ResourceFields.ATRRIBUTE_VALUES);
List<Entity> newAttributes = Lists.newArrayList();
DataDefinition attributeDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER,
MaterialFlowResourcesConstants.MODEL_ATTRIBUTE_VALUE);
for (Entity attribute : attributes) {
List<Entity> newAttribute = attributeDD.copy(attribute.getId());
newAttribute.get(0).setField(AttributeValueFields.RESOURCE, resource);
newAttributes.addAll(newAttribute);
}
resource.setField(ResourceFields.ATRRIBUTE_VALUES, newAttributes);
}
public Entity createResource(final Entity document, final Entity warehouse, final Entity position, final Object date) {
DataDefinition resourceDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER,
MaterialFlowResourcesConstants.MODEL_RESOURCE);
Entity product = position.getBelongsToField(PositionFields.PRODUCT);
Entity resource = resourceDD.create();
Entity user = document.getBelongsToField(DocumentFields.USER);
Entity delivery = document.getBelongsToField(ResourceFields.DELIVERY);
resource.setField(ResourceFields.USER_NAME, user.getStringField(_FIRST_NAME) + " " + user.getStringField(L_LAST_NAME));
resource.setField(ResourceFields.TIME, date);
resource.setField(ResourceFields.LOCATION, warehouse);
resource.setField(ResourceFields.PRODUCT, position.getBelongsToField(PositionFields.PRODUCT));
resource.setField(ResourceFields.QUANTITY, position.getField(PositionFields.QUANTITY));
resource.setField(ResourceFields.RESERVED_QUANTITY, BigDecimal.ZERO);
resource.setField(ResourceFields.AVAILABLE_QUANTITY, position.getDecimalField(PositionFields.QUANTITY));
resource.setField(ResourceFields.PRICE, position.getField(PositionFields.PRICE));
resource.setField(ResourceFields.BATCH, position.getField(PositionFields.BATCH));
resource.setField(ResourceFields.EXPIRATION_DATE, position.getField(PositionFields.EXPIRATION_DATE));
resource.setField(ResourceFields.PRODUCTION_DATE, position.getField(PositionFields.PRODUCTION_DATE));
resource.setField(ResourceFields.STORAGE_LOCATION, position.getField(PositionFields.STORAGE_LOCATION));
resource.setField(ResourceFields.ADDITIONAL_CODE, position.getField(PositionFields.ADDITIONAL_CODE));
resource.setField(ResourceFields.PALLET_NUMBER, position.getField(PositionFields.PALLET_NUMBER));
resource.setField(ResourceFields.TYPE_OF_PALLET, position.getField(PositionFields.TYPE_OF_PALLET));
resource.setField(ResourceFields.WASTE, position.getField(PositionFields.WASTE));
if (delivery != null) {
resource.setField(ResourceFields.DELIVERY_NUMBER, delivery.getStringField("number"));
}
if (StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
resource.setField(ResourceFields.GIVEN_UNIT, product.getField(ProductFields.UNIT));
resource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT, position.getField(PositionFields.QUANTITY));
resource.setField(ResourceFields.CONVERSION, BigDecimal.ONE);
} else {
resource.setField(ResourceFields.GIVEN_UNIT, position.getField(PositionFields.GIVEN_UNIT));
resource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT, position.getField(PositionFields.GIVEN_QUANTITY));
resource.setField(ResourceFields.CONVERSION, position.getField(PositionFields.CONVERSION));
}
setResourceAttributesFromPosition(resource, position);
resourceStockService.addResourceStock(resource);
resource = resourceDD.save(resource);
position.setField("resourceReceiptDocument", resource.getId().toString());
return resource;
}
public Entity createResource(final Entity position, final Entity warehouse, final Entity resource, final BigDecimal quantity,
Object date) {
return createResource(position, warehouse, resource, quantity, date, false);
}
public Entity createResource(final Entity position, final Entity warehouse, final Entity resource, final BigDecimal quantity,
Object date, final boolean assignNewStorageLocation) {
DataDefinition resourceDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER,
MaterialFlowResourcesConstants.MODEL_RESOURCE);
Entity newResource = resourceDD.create();
if (position != null) {
Entity document = position.getBelongsToField(PositionFields.DOCUMENT);
if (document != null) {
Entity user = document.getBelongsToField(DocumentFields.USER);
newResource.setField(ResourceFields.USER_NAME,
user.getStringField(_FIRST_NAME) + " " + user.getStringField(L_LAST_NAME));
}
}
newResource.setField(ResourceFields.TIME, date);
newResource.setField(ResourceFields.LOCATION, warehouse);
newResource.setField(ResourceFields.PRODUCT, resource.getBelongsToField(PositionFields.PRODUCT));
newResource.setField(ResourceFields.QUANTITY, quantity);
newResource.setField(ResourceFields.AVAILABLE_QUANTITY, quantity);
newResource.setField(ResourceFields.RESERVED_QUANTITY, BigDecimal.ZERO);
newResource.setField(ResourceFields.PRICE, resource.getField(PositionFields.PRICE));
newResource.setField(ResourceFields.BATCH, resource.getField(PositionFields.BATCH));
newResource.setField(ResourceFields.EXPIRATION_DATE, resource.getField(PositionFields.EXPIRATION_DATE));
newResource.setField(ResourceFields.PRODUCTION_DATE, resource.getField(PositionFields.PRODUCTION_DATE));
if (!assignNewStorageLocation) {
newResource.setField(ResourceFields.STORAGE_LOCATION, resource.getField(ResourceFields.STORAGE_LOCATION));
newResource.setField(ResourceFields.PALLET_NUMBER, resource.getField(ResourceFields.PALLET_NUMBER));
newResource.setField(ResourceFields.TYPE_OF_PALLET, resource.getField(ResourceFields.TYPE_OF_PALLET));
} else {
newResource.setField(ResourceFields.STORAGE_LOCATION,
findStorageLocationForProduct(warehouse, resource.getBelongsToField(ResourceFields.PRODUCT)));
newResource.setField(ResourceFields.PALLET_NUMBER, null);
newResource.setField(ResourceFields.TYPE_OF_PALLET, null);
}
newResource.setField(ResourceFields.ADDITIONAL_CODE, resource.getField(ResourceFields.ADDITIONAL_CODE));
newResource.setField(ResourceFields.CONVERSION, resource.getField(ResourceFields.CONVERSION));
newResource.setField(ResourceFields.GIVEN_UNIT, resource.getField(ResourceFields.GIVEN_UNIT));
BigDecimal quantityInAdditionalUnit = numberService.setScale(quantity.multiply(resource
.getDecimalField(ResourceFields.CONVERSION)));
newResource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT, quantityInAdditionalUnit);
setResourceAttributesFromResource(newResource, resource);
resourceStockService.addResourceStock(newResource);
return resourceDD.save(newResource);
}
private Entity findStorageLocationForProduct(final Entity warehouse, final Entity product) {
List<Entity> storageLocations = dataDefinitionService
.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_STORAGE_LOCATION)
.find().add(SearchRestrictions.belongsTo(StorageLocationFields.LOCATION, warehouse))
.add(SearchRestrictions.belongsTo(StorageLocationFields.PRODUCT, product)).list().getEntities();
if (storageLocations.isEmpty()) {
return null;
} else {
return storageLocations.get(0);
}
}
public Entity createResource(final Entity warehouse, final Entity resource, final BigDecimal quantity, Object date) {
return createResource(null, warehouse, resource, quantity, date);
}
private SearchCriteriaBuilder getSearchCriteriaForResourceForProductAndWarehouse(final Entity product, final Entity warehouse) {
return dataDefinitionService
.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE).find()
.add(SearchRestrictions.belongsTo(ResourceFields.LOCATION, warehouse))
.add(SearchRestrictions.belongsTo(ResourceFields.PRODUCT, product))
.add(SearchRestrictions.gt(ResourceFields.AVAILABLE_QUANTITY, BigDecimal.ZERO));
}
public Multimap<Long, BigDecimal> getQuantitiesInWarehouse(final Entity warehouse,
Multimap<Entity, Entity> productsAndPositions) {
Multimap<Long, BigDecimal> result = ArrayListMultimap.create();
String algorithm = warehouse.getStringField(LocationFieldsMFR.ALGORITHM);
for (Map.Entry<Entity, Entity> productAndPosition : productsAndPositions.entries()) {
Entity resource = productAndPosition.getValue().getBelongsToField(PositionFields.RESOURCE);
if (algorithm.equalsIgnoreCase(WarehouseAlgorithm.MANUAL.getStringValue()) && resource != null) {
result.put(productAndPosition.getKey().getId(), resource.getDecimalField(ResourceFields.AVAILABLE_QUANTITY));
} else {
Entity additionalCode = productAndPosition.getValue().getBelongsToField(PositionFields.ADDITIONAL_CODE);
BigDecimal conversion = productAndPosition.getValue().getDecimalField(PositionFields.CONVERSION);
Entity reservation = reservationsService.getReservationForPosition(productAndPosition.getValue());
List<Entity> resources = Lists.newArrayList();
if (additionalCode != null) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(productAndPosition.getKey(),
warehouse);
if (!StringUtils.isEmpty(productAndPosition.getKey().getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, conversion));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.add(SearchRestrictions.belongsTo(ResourceFields.ADDITIONAL_CODE, additionalCode)).list()
.getEntities();
scb = getSearchCriteriaForResourceForProductAndWarehouse(productAndPosition.getKey(), warehouse);
if (!StringUtils.isEmpty(productAndPosition.getKey().getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, conversion));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources.addAll(scb
.add(SearchRestrictions.or(SearchRestrictions.isNull(ResourceFields.ADDITIONAL_CODE),
SearchRestrictions.ne("additionalCode.id", additionalCode.getId()))).list().getEntities());
}
if (resources.isEmpty()) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(productAndPosition.getKey(),
warehouse);
if (!StringUtils.isEmpty(productAndPosition.getKey().getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, conversion));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.list().getEntities();
}
BigDecimal reservedQuantity = BigDecimal.ZERO;
if (reservation != null) {
reservedQuantity = reservation.getDecimalField(ReservationFields.QUANTITY);
}
if (result.containsKey(productAndPosition.getKey().getId())) {
BigDecimal currentQuantity = result.get(productAndPosition.getKey().getId()).stream()
.reduce(reservedQuantity, BigDecimal::add);
result.put(productAndPosition.getKey().getId(),
(resources.stream().map(res -> res.getDecimalField(ResourceFields.AVAILABLE_QUANTITY)).reduce(
BigDecimal.ZERO, BigDecimal::add)).add(currentQuantity));
} else {
result.put(
productAndPosition.getKey().getId(),
resources.stream().map(res -> res.getDecimalField(ResourceFields.AVAILABLE_QUANTITY))
.reduce(reservedQuantity, BigDecimal::add));
}
}
}
return result;
}
public BigDecimal getQuantityOfProductInWarehouse(final Entity warehouse, final Entity product, Entity position) {
BigDecimal quantity = BigDecimal.ZERO;
Entity resource = position.getBelongsToField(PositionFields.RESOURCE);
Entity reservation = reservationsService.getReservationForPosition(position);
if (resource != null) {
quantity = resource.getDecimalField(ResourceFields.AVAILABLE_QUANTITY);
} else {
List<Entity> resources = dataDefinitionService
.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE).find()
.add(SearchRestrictions.belongsTo(ResourceFields.LOCATION, warehouse))
.add(SearchRestrictions.belongsTo(ResourceFields.PRODUCT, product)).list().getEntities();
for (Entity res : resources) {
quantity = quantity.add(res.getDecimalField(ResourceFields.AVAILABLE_QUANTITY));
}
}
if (reservation != null) {
quantity = quantity.add(reservation.getDecimalField(ReservationFields.QUANTITY));
}
return quantity;
}
private Multimap<Entity, Entity> getProductsAndPositionsFromDocument(final Entity document) {
Multimap<Entity, Entity> map = ArrayListMultimap.create();
List<Entity> positions = document.getHasManyField(DocumentFields.POSITIONS);
positions.stream().forEach(position -> map.put(position.getBelongsToField(PositionFields.PRODUCT), position));
return map;
}
private BigDecimal getQuantityOfProductFromMultimap(final Multimap<Long, BigDecimal> quantitiesForWarehouse,
final Entity product) {
List<BigDecimal> quantities = Lists.newArrayList(quantitiesForWarehouse.get(product.getId()));
return quantities.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
@Transactional
public void updateResourcesForReleaseDocuments(final Entity document) {
Entity warehouse = document.getBelongsToField(DocumentFields.LOCATION_FROM);
WarehouseAlgorithm warehouseAlgorithm;
boolean enoughResources = true;
StringBuilder errorMessage = new StringBuilder();
Multimap<Long, BigDecimal> quantitiesForWarehouse = getQuantitiesInWarehouse(warehouse,
getProductsAndPositionsFromDocument(document));
List<Entity> generatedPositions = Lists.newArrayList();
for (Entity position : document.getHasManyField(DocumentFields.POSITIONS)) {
Entity product = position.getBelongsToField(PositionFields.PRODUCT);
Entity resource = position.getBelongsToField(PositionFields.RESOURCE);
BigDecimal quantityInWarehouse;
if (resource != null) {
warehouse = resource.getBelongsToField(ResourceFields.LOCATION);
warehouseAlgorithm = WarehouseAlgorithm.MANUAL;
} else {
warehouse = document.getBelongsToField(DocumentFields.LOCATION_FROM);
warehouseAlgorithm = WarehouseAlgorithm.parseString(warehouse.getStringField(LocationFieldsMFR.ALGORITHM));
}
if (warehouseAlgorithm.equals(WarehouseAlgorithm.MANUAL)) {
quantityInWarehouse = getQuantityOfProductInWarehouse(warehouse, product, position);
} else {
quantityInWarehouse = getQuantityOfProductFromMultimap(quantitiesForWarehouse, product);
}
generatedPositions.addAll(updateResources(warehouse, position, warehouseAlgorithm));
enoughResources = enoughResources && position.isValid();
if (!position.isValid()) {
BigDecimal quantity = position.getDecimalField(QUANTITY);
errorMessage.append(product.getStringField(ProductFields.NAME));
errorMessage.append(" - ");
errorMessage.append(numberService.format(quantity.subtract(quantityInWarehouse)));
errorMessage.append(" ");
errorMessage.append(product.getStringField(ProductFields.UNIT));
errorMessage.append(", ");
}
}
if (!enoughResources) {
addDocumentError(document, warehouse, errorMessage);
} else {
deleteReservations(document);
document.setField(DocumentFields.POSITIONS, generatedPositions);
}
}
private void deleteReservations(Entity document) {
List<Entity> positions = document.getHasManyField(DocumentFields.POSITIONS);
for (Entity position : positions) {
reservationsService.deleteReservationFromDocumentPosition(position);
}
}
private List<Entity> updateResources(Entity warehouse, Entity position, WarehouseAlgorithm warehouseAlgorithm) {
DataDefinition positionDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER,
MaterialFlowResourcesConstants.MODEL_POSITION);
List<Entity> newPositions = Lists.newArrayList();
Entity product = position.getBelongsToField(PositionFields.PRODUCT);
List<Entity> resources = getResourcesForWarehouseProductAndAlgorithm(warehouse, product, position, warehouseAlgorithm);
BigDecimal quantity = position.getDecimalField(PositionFields.QUANTITY);
resourceStockService.removeResourceStock(product, warehouse, quantity);
for (Entity resource : resources) {
BigDecimal resourceQuantity = resource.getDecimalField(ResourceFields.QUANTITY);
BigDecimal resourceAvailableQuantity = resource.getDecimalField(ResourceFields.AVAILABLE_QUANTITY);
Entity newPosition = positionDD.create();
newPosition.setField(PositionFields.PRODUCT, position.getBelongsToField(PositionFields.PRODUCT));
newPosition.setField(PositionFields.GIVEN_QUANTITY, position.getDecimalField(PositionFields.GIVEN_QUANTITY));
newPosition.setField(PositionFields.GIVEN_UNIT, position.getStringField(PositionFields.GIVEN_UNIT));
newPosition.setField(PositionFields.WASTE, resource.getBooleanField(ResourceFields.WASTE));
newPosition.setField(PositionFields.PRICE, resource.getField(ResourceFields.PRICE));
newPosition.setField(PositionFields.BATCH, resource.getField(ResourceFields.BATCH));
newPosition.setField(PositionFields.PRODUCTION_DATE, resource.getField(ResourceFields.PRODUCTION_DATE));
newPosition.setField(PositionFields.EXPIRATION_DATE, resource.getField(ResourceFields.EXPIRATION_DATE));
newPosition.setField(PositionFields.RESOURCE, resource);
newPosition.setField(PositionFields.STORAGE_LOCATION, resource.getField(ResourceFields.STORAGE_LOCATION));
newPosition.setField(PositionFields.ADDITIONAL_CODE, resource.getField(ResourceFields.ADDITIONAL_CODE));
newPosition.setField(PositionFields.CONVERSION, position.getField(PositionFields.CONVERSION));
newPosition.setField(PositionFields.PALLET_NUMBER, resource.getField(ResourceFields.PALLET_NUMBER));
newPosition.setField(PositionFields.TYPE_OF_PALLET, resource.getField(ResourceFields.TYPE_OF_PALLET));
// newPosition.setField(PositionFields.GIVEN_UNIT, resource.getField(ResourceFields.GIVEN_UNIT));
setPositionAttributesFromResource(newPosition, resource);
if (quantity.compareTo(resourceAvailableQuantity) >= 0) {
quantity = quantity.subtract(resourceAvailableQuantity, numberService.getMathContext());
if (resourceQuantity.compareTo(resourceAvailableQuantity) <= 0) {
Entity palletNumberToDispose = resource.getBelongsToField(ResourceFields.PALLET_NUMBER);
resource.getDataDefinition().delete(resource.getId());
palletNumberDisposalService.tryToDispose(palletNumberToDispose);
} else {
BigDecimal newResourceQuantity = resourceQuantity.subtract(resourceAvailableQuantity);
BigDecimal resourceConversion = resource.getDecimalField(ResourceFields.CONVERSION);
BigDecimal quantityInAdditionalUnit = newResourceQuantity.multiply(resourceConversion);
resource.setField(ResourceFields.AVAILABLE_QUANTITY, BigDecimal.ZERO);
resource.setField(ResourceFields.QUANTITY, newResourceQuantity);
resource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT,
numberService.setScale(quantityInAdditionalUnit));
resource.getDataDefinition().save(resource);
}
newPosition.setField(PositionFields.QUANTITY, numberService.setScale(resourceAvailableQuantity));
BigDecimal givenResourceQuantity = convertToGivenUnit(resourceAvailableQuantity, position);
newPosition.setField(PositionFields.GIVEN_QUANTITY, numberService.setScale(givenResourceQuantity));
newPositions.add(newPosition);
if (BigDecimal.ZERO.compareTo(quantity) == 0) {
return newPositions;
}
} else {
resourceQuantity = resourceQuantity.subtract(quantity, numberService.getMathContext());
resourceAvailableQuantity = resourceAvailableQuantity.subtract(quantity, numberService.getMathContext());
if (position.getBelongsToField(PositionFields.RESOURCE) != null
&& reservationsService.reservationsEnabledForDocumentPositions()) {
BigDecimal reservedQuantity = resource.getDecimalField(ResourceFields.RESERVED_QUANTITY).subtract(quantity,
numberService.getMathContext());
resource.setField(ResourceFields.RESERVED_QUANTITY, reservedQuantity);
}
BigDecimal resourceConversion = resource.getDecimalField(ResourceFields.CONVERSION);
BigDecimal quantityInAdditionalUnit = resourceQuantity.multiply(resourceConversion);
resource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT, numberService.setScale(quantityInAdditionalUnit));
resource.setField(ResourceFields.QUANTITY, numberService.setScale(resourceQuantity));
resource.setField(ResourceFields.AVAILABLE_QUANTITY, resourceAvailableQuantity);
resource.getDataDefinition().save(resource);
newPosition.setField(PositionFields.QUANTITY, numberService.setScale(quantity));
BigDecimal givenQuantity = convertToGivenUnit(quantity, position);
newPosition.setField(PositionFields.GIVEN_QUANTITY, numberService.setScale(givenQuantity));
newPositions.add(newPosition);
return newPositions;
}
}
position.addError(position.getDataDefinition().getField(PositionFields.QUANTITY),
"materialFlow.error.position.quantity.notEnough");
return Lists.newArrayList(position);
}
public BigDecimal convertToGivenUnit(BigDecimal quantity, Entity position) {
Entity product = position.getBelongsToField(PositionFields.PRODUCT);
String baseUnit = product.getStringField(ProductFields.UNIT);
String givenUnit = position.getStringField(PositionFields.GIVEN_UNIT);
if (!baseUnit.equals(givenUnit)) {
PossibleUnitConversions unitConversions = unitConversionService.getPossibleConversions(baseUnit,
searchCriteriaBuilder -> searchCriteriaBuilder.add(SearchRestrictions.belongsTo(
UnitConversionItemFieldsB.PRODUCT, product)));
if (unitConversions.isDefinedFor(givenUnit)) {
return unitConversions.convertTo(quantity, givenUnit);
}
}
return quantity;
}
private BigDecimal convertToGivenUnit(BigDecimal quantity, Entity product, String givenUnit) {
String baseUnit = product.getStringField(ProductFields.UNIT);
if (!baseUnit.equals(givenUnit)) {
PossibleUnitConversions unitConversions = unitConversionService.getPossibleConversions(baseUnit,
searchCriteriaBuilder -> searchCriteriaBuilder.add(SearchRestrictions.belongsTo(
UnitConversionItemFieldsB.PRODUCT, product)));
if (unitConversions.isDefinedFor(givenUnit)) {
return unitConversions.convertTo(quantity, givenUnit);
}
}
return quantity;
}
@Override
@Transactional
public void moveResourcesForTransferDocument(Entity document) {
Entity warehouseFrom = document.getBelongsToField(DocumentFields.LOCATION_FROM);
Entity warehouseTo = document.getBelongsToField(DocumentFields.LOCATION_TO);
Object date = document.getField(DocumentFields.TIME);
WarehouseAlgorithm warehouseAlgorithm = WarehouseAlgorithm.parseString(warehouseFrom
.getStringField(LocationFieldsMFR.ALGORITHM));
boolean enoughResources = true;
StringBuilder errorMessage = new StringBuilder();
Multimap<Long, BigDecimal> quantitiesForWarehouse = getQuantitiesInWarehouse(warehouseFrom,
getProductsAndPositionsFromDocument(document));
for (Entity position : document.getHasManyField(DocumentFields.POSITIONS)) {
Entity product = position.getBelongsToField(PositionFields.PRODUCT);
BigDecimal quantityInWarehouse;
if (warehouseAlgorithm.equals(WarehouseAlgorithm.MANUAL)) {
quantityInWarehouse = getQuantityOfProductInWarehouse(warehouseFrom, product, position);
} else {
quantityInWarehouse = getQuantityOfProductFromMultimap(quantitiesForWarehouse, product);
}
moveResources(warehouseFrom, warehouseTo, position, date, warehouseAlgorithm);
enoughResources = enoughResources && position.isValid();
if (!position.isValid()) {
BigDecimal quantity = position.getDecimalField(QUANTITY);
errorMessage.append(product.getStringField(ProductFields.NAME));
errorMessage.append(" - ");
errorMessage.append(numberService.format(quantity.subtract(quantityInWarehouse)));
errorMessage.append(" ");
errorMessage.append(product.getStringField(ProductFields.UNIT));
errorMessage.append(", ");
}
}
if (!enoughResources) {
addDocumentError(document, warehouseFrom, errorMessage);
} else {
deleteReservations(document);
}
}
private void updateReservations(Entity document) {
List<Entity> positions = document.getHasManyField(DocumentFields.POSITIONS);
for (Entity position : positions) {
reservationsService.updateReservationFromDocumentPosition(position);
}
}
private void addDocumentError(final Entity document, final Entity warehouseFrom, final StringBuilder errorMessage) {
String warehouseName = warehouseFrom.getStringField(LocationFields.NAME);
if ((errorMessage.toString().length() + warehouseName.length()) < 255) {
document.addGlobalError("materialFlow.error.position.quantity.notEnoughResources", false, errorMessage.toString(),
warehouseName);
} else {
document.addGlobalError("materialFlow.error.position.quantity.notEnoughResourcesShort", false);
}
}
private void moveResources(Entity warehouseFrom, Entity warehouseTo, Entity position, Object date,
WarehouseAlgorithm warehouseAlgorithm) {
Entity product = position.getBelongsToField(PositionFields.PRODUCT);
List<Entity> resources = getResourcesForWarehouseProductAndAlgorithm(warehouseFrom, product, position, warehouseAlgorithm);
DataDefinition positionDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER,
MaterialFlowResourcesConstants.MODEL_POSITION);
BigDecimal quantity = position.getDecimalField(PositionFields.QUANTITY);
resourceStockService.removeResourceStock(product, warehouseFrom, quantity);
for (Entity resource : resources) {
BigDecimal resourceQuantity = resource.getDecimalField(QUANTITY);
BigDecimal resourceAvailableQuantity = resource.getDecimalField(ResourceFields.AVAILABLE_QUANTITY);
if (quantity.compareTo(resourceAvailableQuantity) >= 0) {
quantity = quantity.subtract(resourceAvailableQuantity, numberService.getMathContext());
if (resourceQuantity.compareTo(resourceAvailableQuantity) <= 0) {
Entity palletNumberToDispose = resource.getBelongsToField(ResourceFields.PALLET_NUMBER);
resource.getDataDefinition().delete(resource.getId());
palletNumberDisposalService.tryToDispose(palletNumberToDispose);
} else {
BigDecimal newResourceQuantity = resourceQuantity.subtract(resourceAvailableQuantity);
BigDecimal resourceConversion = resource.getDecimalField(ResourceFields.CONVERSION);
BigDecimal quantityInAdditionalUnit = newResourceQuantity.multiply(resourceConversion);
resource.setField(ResourceFields.AVAILABLE_QUANTITY, BigDecimal.ZERO);
resource.setField(ResourceFields.QUANTITY, newResourceQuantity);
resource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT,
numberService.setScale(quantityInAdditionalUnit));
resource.getDataDefinition().save(resource);
}
Entity newResource = createResource(position, warehouseTo, resource, resourceAvailableQuantity, date, true);
if (BigDecimal.ZERO.compareTo(quantity) == 0) {
if (newResource.isValid()) {
return;
} else {
for (Map.Entry<String, ErrorMessage> error : newResource.getErrors().entrySet()) {
position.addError(positionDD.getField(error.getKey()), error.getValue().getMessage());
}
}
}
} else {
resourceQuantity = resourceQuantity.subtract(quantity, numberService.getMathContext());
resourceAvailableQuantity = resourceAvailableQuantity.subtract(quantity, numberService.getMathContext());
String givenUnit = resource.getStringField(ResourceFields.GIVEN_UNIT);
BigDecimal quantityInAdditionalUnit = convertToGivenUnit(resourceQuantity, product, givenUnit);
resource.setField(ResourceFields.QUANTITY_IN_ADDITIONAL_UNIT, numberService.setScale(quantityInAdditionalUnit));
resource.setField(ResourceFields.QUANTITY, numberService.setScale(resourceQuantity));
resource.setField(ResourceFields.AVAILABLE_QUANTITY, resourceAvailableQuantity);
resource.getDataDefinition().save(resource);
Entity newResource = createResource(position, warehouseTo, resource, quantity, date, true);
if (newResource.isValid()) {
return;
} else {
for (Map.Entry<String, ErrorMessage> error : newResource.getErrors().entrySet()) {
position.addError(positionDD.getField(error.getKey()), error.getValue().getMessage());
}
}
}
}
position.addError(position.getDataDefinition().getField(PositionFields.QUANTITY),
"materialFlow.error.position.quantity.notEnough");
}
@Override
public List<Entity> getResourcesForWarehouseProductAndAlgorithm(Entity warehouse, Entity product, Entity position,
WarehouseAlgorithm warehouseAlgorithm) {
List<Entity> resources = Lists.newArrayList();
Entity resource = position.getBelongsToField(PositionFields.RESOURCE);
Entity additionalCode = position.getBelongsToField(PositionFields.ADDITIONAL_CODE);
if (resource != null) {
Entity reservation = reservationsService.getReservationForPosition(position);
if (reservation != null) {
BigDecimal reservationQuantity = reservation.getDecimalField(ReservationFields.QUANTITY);
BigDecimal resourceAvailableQuantity = resource.getDecimalField(ResourceFields.AVAILABLE_QUANTITY);
resource.setField(ResourceFields.AVAILABLE_QUANTITY, resourceAvailableQuantity.add(reservationQuantity));
}
resources.add(resource);
} else if (WarehouseAlgorithm.FIFO.equals(warehouseAlgorithm)) {
resources = getResourcesForLocationAndProductFIFO(warehouse, product, additionalCode, position);
} else if (WarehouseAlgorithm.LIFO.equals(warehouseAlgorithm)) {
resources = getResourcesForLocationAndProductLIFO(warehouse, product, additionalCode, position);
} else if (WarehouseAlgorithm.FEFO.equals(warehouseAlgorithm)) {
resources = getResourcesForLocationAndProductFEFO(warehouse, product, additionalCode, position);
} else if (WarehouseAlgorithm.LEFO.equals(warehouseAlgorithm)) {
resources = getResourcesForLocationAndProductLEFO(warehouse, product, additionalCode, position);
} else if (WarehouseAlgorithm.MANUAL.equals(warehouseAlgorithm)) {
resources = getResourcesForLocationAndProductMANUAL(warehouse, product, position, additionalCode);
}
return resources;
}
private List<Entity> getResourcesForLocationAndProductMANUAL(final Entity warehouse, final Entity product,
final Entity position, final Entity additionalCode) {
Entity resource = position.getBelongsToField(PositionFields.RESOURCE);
if (resource != null) {
return Lists.newArrayList(resource);
} else {
return getResourcesForLocationAndProductFIFO(warehouse, product, additionalCode, position);
}
}
private List<Entity> getResourcesForLocationAndProductFIFO(final Entity warehouse, final Entity product,
final Entity additionalCode, final Entity position) {
List<Entity> resources = Lists.newArrayList();
if (additionalCode != null) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.add(SearchRestrictions.belongsTo(ResourceFields.ADDITIONAL_CODE, additionalCode))
.addOrder(SearchOrders.asc(TIME)).list().getEntities();
scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources.addAll(scb
.add(SearchRestrictions.or(SearchRestrictions.isNull(ResourceFields.ADDITIONAL_CODE),
SearchRestrictions.ne("additionalCode.id", additionalCode.getId()))).addOrder(SearchOrders.asc(TIME))
.list().getEntities());
}
if (resources.isEmpty()) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.addOrder(SearchOrders.asc(TIME)).list().getEntities();
}
return resources;
}
private List<Entity> getResourcesForLocationAndProductLIFO(final Entity warehouse, final Entity product,
final Entity additionalCode, final Entity position) {
List<Entity> resources = Lists.newArrayList();
if (additionalCode != null) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.add(SearchRestrictions.belongsTo(ResourceFields.ADDITIONAL_CODE, additionalCode))
.addOrder(SearchOrders.desc(TIME)).list().getEntities();
scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources.addAll(scb
.add(SearchRestrictions.or(SearchRestrictions.isNull(ResourceFields.ADDITIONAL_CODE),
SearchRestrictions.ne("additionalCode.id", additionalCode.getId())))
.addOrder(SearchOrders.desc(TIME)).list().getEntities());
}
if (resources.isEmpty()) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.addOrder(SearchOrders.desc(TIME)).list().getEntities();
}
return resources;
}
private List<Entity> getResourcesForLocationAndProductFEFO(final Entity warehouse, final Entity product,
final Entity additionalCode, final Entity position) {
List<Entity> resources = Lists.newArrayList();
if (additionalCode != null) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.add(SearchRestrictions.belongsTo(ResourceFields.ADDITIONAL_CODE, additionalCode))
.addOrder(SearchOrders.asc(ResourceFields.EXPIRATION_DATE)).list().getEntities();
scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources.addAll(scb
.add(SearchRestrictions.or(SearchRestrictions.isNull(ResourceFields.ADDITIONAL_CODE),
SearchRestrictions.ne("additionalCode.id", additionalCode.getId())))
.addOrder(SearchOrders.asc(ResourceFields.EXPIRATION_DATE)).list().getEntities());
}
if (resources.isEmpty()) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.addOrder(SearchOrders.asc(ResourceFields.EXPIRATION_DATE)).list().getEntities();
}
return resources;
}
private List<Entity> getResourcesForLocationAndProductLEFO(final Entity warehouse, final Entity product,
final Entity additionalCode, final Entity position) {
List<Entity> resources = Lists.newArrayList();
if (additionalCode != null) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.add(SearchRestrictions.belongsTo(ResourceFields.ADDITIONAL_CODE, additionalCode))
.addOrder(SearchOrders.desc(ResourceFields.EXPIRATION_DATE)).list().getEntities();
scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources.addAll(scb
.add(SearchRestrictions.or(SearchRestrictions.isNull(ResourceFields.ADDITIONAL_CODE),
SearchRestrictions.ne("additionalCode.id", additionalCode.getId())))
.addOrder(SearchOrders.desc(ResourceFields.EXPIRATION_DATE)).list().getEntities());
}
if (resources.isEmpty()) {
SearchCriteriaBuilder scb = getSearchCriteriaForResourceForProductAndWarehouse(product, warehouse);
if (!StringUtils.isEmpty(product.getStringField(ProductFields.ADDITIONAL_UNIT))) {
scb.add(SearchRestrictions.eq(PositionFields.CONVERSION, position.getDecimalField(PositionFields.CONVERSION)));
} else {
scb.add(SearchRestrictions.eq(ResourceFields.CONVERSION, BigDecimal.ONE));
}
resources = scb.addOrder(SearchOrders.desc(ResourceFields.EXPIRATION_DATE)).list().getEntities();
}
return resources;
}
}