package com.qcadoo.mes.deliveries;
import com.qcadoo.mes.deliveries.constants.*;
import com.qcadoo.mes.materialFlow.constants.LocationFields;
import com.qcadoo.model.api.*;
import com.qcadoo.model.api.search.SearchCriteriaBuilder;
import com.qcadoo.model.api.search.SearchCriterion;
import com.qcadoo.model.api.search.SearchRestrictions;
import com.qcadoo.plugin.api.PluginUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class ReservationService {
public static final String OFFER = "offer";
public static final String OPERATION = "operation";
@Autowired
private DataDefinitionService dataDefinitionService;
public Entity createDefaultReservationsForDeliveredProduct(Entity deliveredProduct) {
Entity product = deliveredProduct.getBelongsToField(DeliveredProductFields.PRODUCT);
if (product != null && !deliveredProduct.getBooleanField(DeliveredProductFields.IS_WASTE)) {
List<Entity> deliveredProductReservations = new ArrayList<>();
Entity orderedProductForProduct = findOrderedProductForProduct(deliveredProduct);
List<Entity> presentDeliveredProductForProductReservations = findPresentDeliveredProductForProductReservations(
deliveredProduct);
if (orderedProductForProduct != null) {
EntityList reservationsFromOrderedProduct = orderedProductForProduct
.getHasManyField(OrderedProductFields.RESERVATIONS);
BigDecimal damagedQuantity = deliveredProduct.getDecimalField(DeliveredProductFields.DAMAGED_QUANTITY);
damagedQuantity = damagedQuantity == null ? BigDecimal.ZERO : damagedQuantity;
BigDecimal availableQuantity = deliveredProduct.getDecimalField(DeliveredProductFields.DELIVERED_QUANTITY);
availableQuantity = availableQuantity == null ? BigDecimal.ZERO : availableQuantity;
availableQuantity = availableQuantity.subtract(damagedQuantity);
for (Entity reservationFromOrderedProduct : reservationsFromOrderedProduct) {
Optional<Entity> maybeDeliveredProductReservation = createDeliveredProductReservation(availableQuantity,
deliveredProduct, reservationFromOrderedProduct, presentDeliveredProductForProductReservations);
if (maybeDeliveredProductReservation.isPresent()) {
Entity deliveredProductReservation = maybeDeliveredProductReservation.get();
deliveredProductReservations.add(deliveredProductReservation);
presentDeliveredProductForProductReservations.add(deliveredProductReservation);
availableQuantity = availableQuantity.subtract(deliveredProductReservation
.getDecimalField(DeliveredProductReservationFields.DELIVERED_QUANTITY));
}
}
}
deliveredProduct.setField(DeliveredProductFields.RESERVATIONS, deliveredProductReservations);
}
return deliveredProduct;
}
private Optional<Entity> createDeliveredProductReservation(BigDecimal availableQuantity, final Entity deliveredProduct,
final Entity reservationFromOrderedProduct, List<Entity> presentDeliveredProductForProductReservations) {
Entity location = reservationFromOrderedProduct.getBelongsToField(OrderedProductReservationFields.LOCATION);
List<Entity> reservationsFromLocation = filterReservationsByLocation(presentDeliveredProductForProductReservations,
location);
BigDecimal orderedQuantity = reservationFromOrderedProduct.getDecimalField(OrderedProductFields.ORDERED_QUANTITY);
BigDecimal deliveredQuantity = sumQuantityFromReservations(reservationsFromLocation,
DeliveredProductReservationFields.DELIVERED_QUANTITY);
BigDecimal requestQuantity = orderedQuantity.subtract(deliveredQuantity);
BigDecimal currentDeliveredQuantity = requestQuantity.compareTo(BigDecimal.ZERO) <= 0 ? BigDecimal.ZERO
: requestQuantity.min(availableQuantity);
BigDecimal conversion = deliveredProduct.getDecimalField(DeliveredProductFields.CONVERSION);
if (conversion == null) {
return Optional.empty();
}
BigDecimal currentDeliveredAdditionalQuantity = currentDeliveredQuantity.multiply(conversion);
if (currentDeliveredQuantity.compareTo(BigDecimal.ZERO) <= 0
|| currentDeliveredAdditionalQuantity.compareTo(BigDecimal.ZERO) <= 0) {
return Optional.empty();
}
DataDefinition deliveredProductReservationDD = getDeliveredProductReservationDD();
Entity deliveredProductReservation = deliveredProductReservationDD.create();
deliveredProductReservation.setField(DeliveredProductReservationFields.ADDITIONAL_QUANTITY,
currentDeliveredAdditionalQuantity);
deliveredProductReservation.setField(DeliveredProductReservationFields.ADDITIONAL_QUANTITY_UNIT,
reservationFromOrderedProduct.getStringField(OrderedProductReservationFields.ADDITIONAL_QUANTITY_UNIT));
deliveredProductReservation.setField(DeliveredProductReservationFields.DELIVERED_PRODUCT, deliveredProduct);
deliveredProductReservation.setField(DeliveredProductReservationFields.DELIVERED_QUANTITY, currentDeliveredQuantity);
deliveredProductReservation.setField(DeliveredProductReservationFields.DELIVERED_QUANTITY_UNIT,
reservationFromOrderedProduct.getStringField(OrderedProductReservationFields.ORDERED_QUANTITY_UNIT));
deliveredProductReservation.setField(DeliveredProductReservationFields.LOCATION, location);
return Optional.of(deliveredProductReservation);
}
public void recalculateReservationsForDelivery(Long deliveryId) {
SearchCriterion criterion = SearchRestrictions.eq(DeliveredProductFields.DELIVERY + ".id", deliveryId);
DataDefinition deliveredProductDD = getDeliveredProductDD();
List<Entity> deliveredProducts = deliveredProductDD.find().add(criterion).list().getEntities();
deletePreviousReservations(deliveredProducts);
for (Entity deliveredProduct : deliveredProducts) {
if (!deliveredProduct.getBooleanField(DeliveredProductFields.IS_WASTE)) {
deliveredProduct = createDefaultReservationsForDeliveredProduct(deliveredProduct);
deliveredProductDD.save(deliveredProduct);
}
}
}
private DataDefinition getDeliveredProductDD() {
return dataDefinitionService.get(DeliveriesConstants.PLUGIN_IDENTIFIER, DeliveriesConstants.MODEL_DELIVERED_PRODUCT);
}
private DataDefinition getDeliveredProductReservationDD() {
return dataDefinitionService.get(DeliveriesConstants.PLUGIN_IDENTIFIER,
DeliveriesConstants.MODEL_DELIVERED_PRODUCT_RESERVATION);
}
private boolean deletePreviousReservations(List<Entity> orderedOrDeliveredProducts) {
int countReservations = 0;
for (Entity p : orderedOrDeliveredProducts) {
EntityList reservations = p.getHasManyField(DeliveredProductFields.RESERVATIONS);
countReservations += reservations.size();
reservations.stream().forEach(reservation -> {
reservation.getDataDefinition().delete(reservation.getId());
});
}
return countReservations > 0;
}
private Entity findOrderedProductForProduct(Entity deliveredProduct) {
Entity delivery = deliveredProduct.getBelongsToField(DeliveredProductFields.DELIVERY);
Entity additionalCode = deliveredProduct.getBelongsToField(DeliveredProductFields.ADDITIONAL_CODE);
Entity product = deliveredProduct.getBelongsToField(DeliveredProductFields.PRODUCT);
SearchCriteriaBuilder findOrderedProduct = delivery.getHasManyField(DeliveryFields.ORDERED_PRODUCTS).find();
findOrderedProduct.add(SearchRestrictions.belongsTo(OrderedProductFields.PRODUCT, product));
if (additionalCode == null) {
findOrderedProduct.add(SearchRestrictions.isNull(OrderedProductFields.ADDITIONAL_CODE));
} else {
findOrderedProduct.add(SearchRestrictions.belongsTo(OrderedProductFields.ADDITIONAL_CODE, additionalCode));
}
if(PluginUtils.isEnabled("supplyNegotiations")) {
findOrderedProduct.add(SearchRestrictions.belongsTo(OFFER, deliveredProduct.getBelongsToField(OFFER)));
}
if (PluginUtils.isEnabled("techSubcontrForDeliveries")) {
findOrderedProduct.add(SearchRestrictions.belongsTo(OPERATION, deliveredProduct.getBelongsToField(OPERATION)));
}
Entity orderedProductForProduct = findOrderedProduct.uniqueResult();
return orderedProductForProduct;
}
private List<Entity> findPresentDeliveredProductForProductReservations(Entity deliveredProduct) {
Entity delivery = deliveredProduct.getBelongsToField(DeliveredProductFields.DELIVERY);
Entity additionalCode = deliveredProduct.getBelongsToField(DeliveredProductFields.ADDITIONAL_CODE);
SearchCriteriaBuilder findDeliveredProducts = delivery.getHasManyField(DeliveryFields.DELIVERED_PRODUCTS).find();
findDeliveredProducts.add(SearchRestrictions.belongsTo(DeliveredProductFields.PRODUCT,
deliveredProduct.getBelongsToField(DeliveredProductFields.PRODUCT)));
if (additionalCode == null) {
findDeliveredProducts.add(SearchRestrictions.isNull(DeliveredProductFields.ADDITIONAL_CODE));
} else {
findDeliveredProducts.add(SearchRestrictions.belongsTo(DeliveredProductFields.ADDITIONAL_CODE, additionalCode));
}
if(PluginUtils.isEnabled("supplyNegotiations")) {
findDeliveredProducts.add(SearchRestrictions.belongsTo(OFFER, deliveredProduct.getBelongsToField(OFFER)));
}
List<Entity> deliveredProducts = findDeliveredProducts.list().getEntities();
List<Entity> allReservationsFromDeliveredProducts = deliveredProducts.stream().flatMap(p -> {
return p.getHasManyField(DeliveredProductFields.RESERVATIONS).stream();
}).collect(Collectors.toList());
return allReservationsFromDeliveredProducts;
}
private List<Entity> filterReservationsByLocation(List<Entity> presentDeliveredProductForProductReservations,
Entity location) {
List<Entity> reservationsFromLocation = presentDeliveredProductForProductReservations.stream().filter(reservation -> {
Entity reservationLocation = reservation.getBelongsToField(OrderedProductReservationFields.LOCATION);
return location.getId().equals(reservationLocation.getId());
}).collect(Collectors.toList());
return reservationsFromLocation;
}
private BigDecimal sumQuantityFromReservations(List<Entity> reservationsFromLocation, String field) {
BigDecimal quantity = reservationsFromLocation.stream().map(r -> {
return r.getDecimalField(field);
}).reduce(BigDecimal.ZERO, (r1, r2) -> r1.add(r2));
return quantity;
}
public void deleteReservationsForOrderedProductIfChanged(Entity orderedProduct) {
if (orderedProduct.getId() != null) {
Entity orderedProductFromDB = orderedProduct.getDataDefinition().get(orderedProduct.getId());
List<String> fields = Arrays.asList(OrderedProductFields.ORDERED_QUANTITY, OrderedProductFields.PRODUCT,
OrderedProductFields.ADDITIONAL_CODE);
for (String field : fields) {
if (notEquals(orderedProduct.getField(field), orderedProductFromDB.getField(field))) {
if (deletePreviousReservations(Arrays.asList(orderedProductFromDB))) {
orderedProduct.addGlobalMessage("deliveries.delivery.message.reservationsDeletedFromOrderedProduct");
}
break;
}
}
}
}
public void deleteReservationsForDeliveredProductIfChanged(Entity deliveredProduct) {
if (deliveredProduct.getId() != null) {
Entity deliveredProductFromDB = deliveredProduct.getDataDefinition().get(deliveredProduct.getId());
List<String> fields = Arrays.asList(DeliveredProductFields.DELIVERED_QUANTITY,
DeliveredProductFields.DAMAGED_QUANTITY, DeliveredProductFields.PRODUCT,
DeliveredProductFields.ADDITIONAL_CODE, DeliveredProductFields.IS_WASTE);
for (String field : fields) {
if (notEquals(deliveredProduct.getField(field), deliveredProductFromDB.getField(field))) {
if (deletePreviousReservations(Arrays.asList(deliveredProductFromDB))) {
deliveredProduct.addGlobalMessage("deliveries.delivery.message.reservationsDeletedFromDeliveredProduct");
}
break;
}
}
}
}
private boolean notEquals(Object a, Object b) {
if (a != null && b != null) {
if (a instanceof BigDecimal && b instanceof BigDecimal) {
return ((BigDecimal) a).compareTo((BigDecimal) b) != 0;
}
return !a.equals(b);
} else {
return a != b;
}
}
public boolean validateDeliveryAgainstReservations(Entity delivery) {
return validateDeliveryOrderedProductsAgainstReservations(delivery)
&& validateDeliveryDeliveredProductsAgainstReservations(delivery);
}
private boolean validateDeliveryOrderedProductsAgainstReservations(Entity delivery) {
Entity deliveryLocation = delivery.getBelongsToField(DeliveryFields.LOCATION);
EntityList orderedProducts = delivery.getHasManyField(DeliveryFields.ORDERED_PRODUCTS);
if (orderedProducts != null && deliveryLocation != null) {
for (Entity orderedProduct : orderedProducts) {
EntityList reservations = orderedProduct.getHasManyField(OrderedProductFields.RESERVATIONS);
if (reservations != null) {
for (Entity reservation : reservations) {
Entity reservationLocation = reservation.getBelongsToField(OrderedProductReservationFields.LOCATION);
if (deliveryLocation.getId().equals(reservationLocation.getId())) {
FieldDefinition locationField = delivery.getDataDefinition().getField(DeliveryFields.LOCATION);
delivery.addError(locationField, "deliveries.delivery.error.locationNotUniqueToDelivery",
deliveryLocation.getStringField(LocationFields.NUMBER));
return false;
}
}
}
}
}
return true;
}
private boolean validateDeliveryDeliveredProductsAgainstReservations(Entity delivery) {
Entity deliveryLocation = delivery.getBelongsToField(DeliveryFields.LOCATION);
EntityList deliveredProducts = delivery.getHasManyField(DeliveryFields.DELIVERED_PRODUCTS);
if (deliveredProducts != null && deliveryLocation != null) {
for (Entity deliveredProduct : deliveredProducts) {
EntityList reservations = deliveredProduct.getHasManyField(DeliveredProductFields.RESERVATIONS);
if (reservations != null) {
for (Entity reservation : reservations) {
Entity reservationLocation = reservation.getBelongsToField(DeliveredProductReservationFields.LOCATION);
if (deliveryLocation.getId().equals(reservationLocation.getId())) {
FieldDefinition locationField = delivery.getDataDefinition().getField(DeliveryFields.LOCATION);
delivery.addError(locationField, "deliveries.delivery.error.locationNotUniqueToDelivery",
deliveryLocation.getStringField(LocationFields.NUMBER));
return false;
}
}
}
}
}
return true;
}
}