/** * *************************************************************************** * 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; import static com.qcadoo.mes.basic.constants.ProductFields.NAME; import static com.qcadoo.mes.materialFlow.constants.LocationFields.TYPE; import static com.qcadoo.mes.materialFlow.constants.LocationType.WAREHOUSE; import static com.qcadoo.mes.materialFlow.constants.StockCorrectionFields.LOCATION; 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.TIME; import static com.qcadoo.mes.materialFlowResources.constants.ResourceFields.BATCH; import static com.qcadoo.mes.materialFlowResources.constants.ResourceFields.PRODUCT; import static com.qcadoo.mes.materialFlowResources.constants.ResourceFields.QUANTITY; import static com.qcadoo.mes.materialFlowResources.constants.TransferFieldsMFR.PRICE; import java.math.BigDecimal; import java.text.DateFormat; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.qcadoo.mes.basic.ParameterService; import com.qcadoo.mes.materialFlow.constants.MaterialFlowConstants; import com.qcadoo.mes.materialFlowResources.constants.AttributeFields; import com.qcadoo.mes.materialFlowResources.constants.AttributeValueFields; import com.qcadoo.mes.materialFlowResources.constants.ChangeDateWhenTransferToWarehouseType; import com.qcadoo.mes.materialFlowResources.constants.MaterialFlowResourcesConstants; import com.qcadoo.mes.materialFlowResources.constants.ParameterFieldsMFR; import com.qcadoo.mes.materialFlowResources.constants.ResourceFields; import com.qcadoo.mes.technologies.constants.TechnologiesConstants; 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.JoinType; import com.qcadoo.model.api.search.SearchOrders; import com.qcadoo.model.api.search.SearchQueryBuilder; import com.qcadoo.model.api.search.SearchRestrictions; import com.qcadoo.model.api.search.SearchResult; import com.qcadoo.view.api.ViewDefinitionState; import com.qcadoo.view.api.components.FieldComponent; import com.qcadoo.view.api.components.FormComponent; import com.qcadoo.view.api.components.LookupComponent; @Service public class MaterialFlowResourcesServiceImpl implements MaterialFlowResourcesService { private static final String L_FORM = "form"; @Autowired private DataDefinitionService dataDefinitionService; @Autowired private NumberService numberService; @Autowired private ParameterService parameterService; @Override public boolean areResourcesSufficient(final Entity location, final Entity product, final BigDecimal quantity) { String type = location.getStringField(TYPE); if (isTypeWarehouse(type)) { BigDecimal resourcesQuantity = getResourcesQuantityForLocationAndProduct(location, product); if (resourcesQuantity == null) { return false; } else { return (resourcesQuantity.compareTo(quantity) >= 0); } } else { return true; } } @Override public BigDecimal getResourcesQuantityForLocationAndProduct(final Entity location, final Entity product) { String type = location.getStringField(TYPE); if (isTypeWarehouse(type)) { List<Entity> resources = getResourcesForLocationAndProduct(location, product); if (resources == null) { return null; } else { BigDecimal resourcesQuantity = BigDecimal.ZERO; for (Entity resource : resources) { resourcesQuantity = resourcesQuantity.add(resource.getDecimalField(QUANTITY), numberService.getMathContext()); } return resourcesQuantity; } } else { return null; } } @Transactional @Override public void manageResources(final Entity transfer) { if (transfer == null) { return; } Entity locationFrom = transfer.getBelongsToField(LOCATION_FROM); Entity locationTo = transfer.getBelongsToField(LOCATION_TO); Entity product = transfer.getBelongsToField(PRODUCT); BigDecimal quantity = transfer.getDecimalField(QUANTITY); Date time = (Date) transfer.getField(TIME); BigDecimal price = transfer.getDecimalField(PRICE); if ((locationFrom != null) && isTypeWarehouse(locationFrom.getStringField(TYPE)) && (locationTo != null) && isTypeWarehouse(locationTo.getStringField(TYPE))) { moveResource(locationFrom, locationTo, product, quantity, time, price); } else if ((locationFrom != null) && isTypeWarehouse(locationFrom.getStringField(TYPE))) { updateResource(locationFrom, product, quantity); } else if ((locationTo != null) && isTypeWarehouse(locationTo.getStringField(TYPE))) { addResource(locationTo, product, quantity, time, price); } } private boolean isTypeWarehouse(final String type) { return ((type != null) && WAREHOUSE.getStringValue().equals(type)); } @Override public void addResource(final Entity locationTo, final Entity product, final BigDecimal quantity, final Date time, final BigDecimal price) { addResource(locationTo, product, quantity, time, price, null); } @Override public void addResource(final Entity locationTo, final Entity product, final BigDecimal quantity, final Date time, final BigDecimal price, final String batch) { Entity resource = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE).create(); resource.setField(LOCATION, locationTo); resource.setField(PRODUCT, product); resource.setField(QUANTITY, numberService.setScale(quantity)); resource.setField(TIME, time); resource.setField(BATCH, batch); resource.setField(PRICE, (price == null) ? null : numberService.setScale(price)); resource.getDataDefinition().save(resource); } @Override public void updateResource(final Entity locationFrom, final Entity product, BigDecimal quantity) { List<Entity> resources = getResourcesForLocationAndProduct(locationFrom, product); if (resources != null) { for (Entity resource : resources) { BigDecimal resourceQuantity = resource.getDecimalField(QUANTITY); if (quantity.compareTo(resourceQuantity) >= 0) { quantity = quantity.subtract(resourceQuantity, numberService.getMathContext()); resource.getDataDefinition().delete(resource.getId()); if (BigDecimal.ZERO.compareTo(quantity) == 0) { return; } } else { resourceQuantity = resourceQuantity.subtract(quantity, numberService.getMathContext()); resource.setField(QUANTITY, numberService.setScale(resourceQuantity)); resource.getDataDefinition().save(resource); return; } } } } @Override public void moveResource(final Entity locationFrom, final Entity locationTo, final Entity product, BigDecimal quantity, final Date time, final BigDecimal price) { List<Entity> resources = getResourcesForLocationAndProduct(locationFrom, product); if (resources != null) { for (Entity resource : resources) { BigDecimal resourceQuantity = resource.getDecimalField(QUANTITY); BigDecimal resourcePrice = (price == null) ? resource.getDecimalField(PRICE) : price; String resourceBatch = (price == null) ? resource.getStringField(BATCH) : null; if (quantity.compareTo(resourceQuantity) >= 0) { quantity = quantity.subtract(resourceQuantity, numberService.getMathContext()); resource.getDataDefinition().delete(resource.getId()); addResource(locationTo, product, resourceQuantity, time, resourcePrice, resourceBatch); if (BigDecimal.ZERO.compareTo(quantity) == 0) { return; } } else { resourceQuantity = resourceQuantity.subtract(quantity, numberService.getMathContext()); resource.setField(QUANTITY, numberService.setScale(resourceQuantity)); resource.getDataDefinition().save(resource); addResource(locationTo, product, quantity, time, resourcePrice, resourceBatch); return; } } } } @Override public List<Entity> getWarehouseLocationsFromDB() { return dataDefinitionService.get(MaterialFlowConstants.PLUGIN_IDENTIFIER, MaterialFlowConstants.MODEL_LOCATION).find() .add(SearchRestrictions.eq(TYPE, WAREHOUSE.getStringValue())).list().getEntities(); } @Override public List<Entity> getResourcesForLocationAndProduct(final Entity location, final Entity product) { List<Entity> resources = dataDefinitionService .get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE).find() .add(SearchRestrictions.belongsTo(LOCATION, location)).add(SearchRestrictions.belongsTo(PRODUCT, product)) .addOrder(SearchOrders.asc(TIME)).list().getEntities(); return resources; } @Override public Map<Long, BigDecimal> getQuantitiesForProductsAndLocation(final List<Entity> products, final Entity location) { Map<Long, BigDecimal> quantities = Maps.newHashMap(); if (products.size() > 0) { DataDefinition resourceDD = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE); StringBuilder sb = new StringBuilder(); sb.append("select p.id as product, sum(r.quantity) as quantity "); sb.append("from #materialFlowResources_resource as r "); sb.append("join r.product as p "); sb.append("join r.location as l "); sb.append("group by p.id, l.id "); sb.append("having p.id in (:productIds) "); sb.append("and l.id = :locationId "); SearchQueryBuilder sqb = resourceDD.find(sb.toString()); sqb.setParameter("locationId", location.getId()); sqb.setParameterList("productIds", products.stream().map(product -> product.getId()).collect(Collectors.toList())); List<Entity> productsAndQuantities = sqb.list().getEntities(); productsAndQuantities.stream().forEach( productAndQuantity -> quantities.put((Long) productAndQuantity.getField("product"), productAndQuantity.getDecimalField("quantity"))); } return quantities; } @Override public Map<Entity, BigDecimal> groupResourcesByProduct(final Entity location) { Map<Entity, BigDecimal> productsAndQuantities = new LinkedHashMap<Entity, BigDecimal>(); List<Entity> resources = getResourcesForLocation(location); if (resources != null) { for (Entity resource : resources) { Entity product = resource.getBelongsToField(PRODUCT); BigDecimal quantity = resource.getDecimalField(QUANTITY); if (productsAndQuantities.containsKey(product)) { productsAndQuantities.put(product, productsAndQuantities.get(product).add(quantity, numberService.getMathContext())); } else { productsAndQuantities.put(product, quantity); } } } return productsAndQuantities; } private List<Entity> getResourcesForLocation(final Entity location) { List<Entity> resources = dataDefinitionService .get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE).find() .createAlias(PRODUCT, PRODUCT).add(SearchRestrictions.belongsTo(LOCATION, location)) .addOrder(SearchOrders.asc(PRODUCT + "." + NAME)).list().getEntities(); return resources; } @Override public BigDecimal calculatePrice(final Entity location, final Entity product) { if ((location != null) && (product != null)) { List<Entity> resources = getResourcesForLocationAndProduct(location, product); if (resources != null) { BigDecimal avgPrice = BigDecimal.ZERO; BigDecimal avgQuantity = BigDecimal.ZERO; for (Entity resource : resources) { BigDecimal quantity = resource.getDecimalField(ResourceFields.QUANTITY); BigDecimal price = resource.getDecimalField(ResourceFields.PRICE); if (price != null) { avgPrice = avgPrice.add(quantity.multiply(price, numberService.getMathContext()), numberService.getMathContext()); avgQuantity = avgQuantity.add(quantity, numberService.getMathContext()); } } if (!BigDecimal.ZERO.equals(avgPrice) && !BigDecimal.ZERO.equals(avgQuantity)) { avgPrice = avgPrice.divide(avgQuantity, numberService.getMathContext()); return avgPrice; } } } return null; } @Override public boolean canChangeDateWhenTransferToWarehouse() { String changeDateWhenTransferToWarehouseType = parameterService.getParameter().getStringField( ParameterFieldsMFR.CHANGE_DATE_WHEN_TRANSFER_TO_WAREHOUSE_TYPE); return !ChangeDateWhenTransferToWarehouseType.NEVER.getStringValue().equals(changeDateWhenTransferToWarehouseType); } @Override public boolean shouldValidateDateWhenTransferToWarehouse() { String changeDateWhenTransferToWarehouseType = parameterService.getParameter().getStringField( ParameterFieldsMFR.CHANGE_DATE_WHEN_TRANSFER_TO_WAREHOUSE_TYPE); return ChangeDateWhenTransferToWarehouseType.VALIDATE_WITH_RESOURCES.getStringValue().equals( changeDateWhenTransferToWarehouseType); } @Override public boolean isDateGraterThanResourcesDate(final Date time) { SearchResult searchResult = dataDefinitionService .get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_RESOURCE).find() .add(SearchRestrictions.gt(TIME, time)).list(); return searchResult.getEntities().isEmpty(); } @Override public void disableDateField(final ViewDefinitionState view) { FormComponent form = (FormComponent) view.getComponentByReference(L_FORM); FieldComponent dateField = (FieldComponent) view.getComponentByReference(TIME); LookupComponent locationFromField = (LookupComponent) view.getComponentByReference(LOCATION_FROM); LookupComponent locationToField = (LookupComponent) view.getComponentByReference(LOCATION_TO); Entity locationFrom = locationFromField.getEntity(); Entity locationTo = locationToField.getEntity(); if (form.getEntityId() == null) { if (areLocationsWarehouses(locationFrom, locationTo) && !canChangeDateWhenTransferToWarehouse()) { String currentDate = DateFormat.getDateTimeInstance().format(new Date()); dateField.setFieldValue(currentDate); dateField.setEnabled(false); } else { dateField.setEnabled(true); } } } @Override public boolean isLocationIsWarehouse(final Entity location) { return ((location != null) && WAREHOUSE.getStringValue().equals(location.getStringField(TYPE))); } @Override public boolean areLocationsWarehouses(final Entity locationFrom, final Entity locationTo) { return (isLocationIsWarehouse(locationFrom) || isLocationIsWarehouse(locationTo)); } @Override public List<Entity> getAttributesForPosition(Entity position, Entity warehouse) { List<Entity> attributes = Lists.newArrayList(); List<Entity> attributesForWarehouse = getAttributesForWarehouse(warehouse); if (attributesForWarehouse != null) { for (Entity attributeForWarehouse : attributesForWarehouse) { Entity existingValue = getExistingAttributeValueForPosition(position, attributeForWarehouse); if (existingValue != null) { attributes.add(existingValue); } else { attributes.add(createAttributeValue(position, attributeForWarehouse)); } } } return attributes; } private Entity createAttributeValue(Entity position, Entity attribute) { Entity newAttribute = dataDefinitionService.get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_ATTRIBUTE_VALUE).create(); newAttribute.setField(AttributeValueFields.ATTRIBUTE, attribute); newAttribute.setField(AttributeValueFields.POSITION, position); newAttribute.setField(AttributeValueFields.VALUE, attribute.getField(AttributeFields.DEFAULT_VALUE)); return newAttribute; } private Entity getExistingAttributeValueForPosition(final Entity position, final Entity attribute) { if (position != null && position.getId() != null) { return dataDefinitionService .get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_ATTRIBUTE_VALUE) .find().createAlias(AttributeValueFields.POSITION, "position", JoinType.INNER) .add(SearchRestrictions.belongsTo(AttributeValueFields.ATTRIBUTE, attribute)) .add(SearchRestrictions.eq("position.id", position.getId())).setMaxResults(1).uniqueResult(); } return null; } private List<Entity> getAttributesForWarehouse(final Entity warehouse) { if (warehouse != null) { return dataDefinitionService .get(MaterialFlowResourcesConstants.PLUGIN_IDENTIFIER, MaterialFlowResourcesConstants.MODEL_ATTRIBUTE).find() .add(SearchRestrictions.belongsTo(AttributeFields.LOCATION, warehouse)).list().getEntities(); } return null; } }