diff --git a/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/IAttributeSet.java b/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/IAttributeSet.java index 6214eb21015..1cf7e9e849f 100644 --- a/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/IAttributeSet.java +++ b/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/IAttributeSet.java @@ -31,10 +31,10 @@ import org.compiere.model.I_M_Attribute; /** - * To get an instance from an attribute set instance, one can use in a storage context. + * Goal of this interface: get an instance from an attribute set instance, one can use in a storage context. * + * @see ImmutableAttributeSet * @author metas-dev - * */ public interface IAttributeSet { @@ -86,7 +86,7 @@ public interface IAttributeSet int getValueAsInt(I_M_Attribute attribute); Date getValueAsDate(I_M_Attribute attribute); - + String getValueAsString(I_M_Attribute attribute); /** diff --git a/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/ImmutableAttributeSet.java b/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/ImmutableAttributeSet.java index 87285c5d033..5aad29eee22 100644 --- a/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/ImmutableAttributeSet.java +++ b/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/ImmutableAttributeSet.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.Predicate; import javax.annotation.Nullable; @@ -79,15 +80,15 @@ public static final ImmutableAttributeSet ofValuesIndexByAttributeId( return new ImmutableAttributeSet(attributes.build(), valuesByAttributeId.build()); } - public static ImmutableAttributeSet copyOf(final IAttributeSet attributeSet) + public static ImmutableAttributeSet createSubSet( + @NonNull final IAttributeSet attributeSet, + @NonNull final Predicate filter) { - if (attributeSet instanceof ImmutableAttributeSet) - { - return (ImmutableAttributeSet)attributeSet; - } final Builder builder = builder(); attributeSet.getAttributes() + .stream() + .filter(filter) .forEach(attribute -> { final Object value = attributeSet.getValue(attribute); builder.attributeValue(attribute, value); diff --git a/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/impl/AttributeDAO.java b/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/impl/AttributeDAO.java index c2592ab5456..c507dbc3654 100644 --- a/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/impl/AttributeDAO.java +++ b/de.metas.business/src/main/java/org/adempiere/mm/attributes/api/impl/AttributeDAO.java @@ -130,10 +130,8 @@ public I_M_AttributeValue retrieveAttributeValueOrNull(final I_M_Attribute attri } @Override - public boolean isHighVolumeValuesList(final I_M_Attribute attribute) + public boolean isHighVolumeValuesList(@NonNull final I_M_Attribute attribute) { - Check.assumeNotNull(attribute, "attribute not null"); - if (!X_M_Attribute.ATTRIBUTEVALUETYPE_List.equals(attribute.getAttributeValueType())) { return false; @@ -149,7 +147,7 @@ public List retrieveAttributeInstances(final I_M_Attribut { return ImmutableList.of(); } - + final Properties ctx = InterfaceWrapperHelper.getCtx(attributeSetInstance); final String trxName = InterfaceWrapperHelper.getTrxName(attributeSetInstance); final int asiId = attributeSetInstance.getM_AttributeSetInstance_ID(); diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/IAttributeValue.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/IAttributeValue.java index 0b6c34ae175..0062475fa3a 100644 --- a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/IAttributeValue.java +++ b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/IAttributeValue.java @@ -10,12 +10,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 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 * . @@ -40,12 +40,6 @@ import de.metas.handlingunits.attribute.strategy.IHUAttributeTransferStrategy; import de.metas.handlingunits.model.X_M_HU_PI_Attribute; -/** - * Defines an {@link I_M_Attribute} - Value pair - * - * @author tsa - * - */ public interface IAttributeValue { /** @@ -217,7 +211,7 @@ public interface IAttributeValue boolean isUseInASI(); /** - * + * * @return *
    *
  • true if this attribute was defined by the standard template diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/storage/IAttributeStorage.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/storage/IAttributeStorage.java index f913a0ac226..34b0dc354ed 100644 --- a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/storage/IAttributeStorage.java +++ b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/attribute/storage/IAttributeStorage.java @@ -51,9 +51,6 @@ /** * Defines a Attribute Storage pool. Use e.g. {@link IAttributeStorageFactory#getAttributeStorage(Object)} do get an instance. - * - * @author tsa - * */ public interface IAttributeStorage extends IAttributeSet { @@ -61,8 +58,6 @@ public interface IAttributeStorage extends IAttributeSet * Get's storage unique identifier. * * This identifier is used to uniquely identify an {@link IAttributeStorage} in children {@link IAttributeStorage} collection (internally). - * - * @return ID */ String getId(); @@ -191,7 +186,7 @@ public interface IAttributeStorage extends IAttributeSet * @return true if given attribute is readonly for user */ boolean isReadonlyUI(final IAttributeValueContext ctx, I_M_Attribute attribute); - + boolean isDisplayedUI(final I_M_Attribute attribute); /** @@ -323,7 +318,7 @@ public interface IAttributeStorage extends IAttributeSet /** * Enables/Disables automatic saving when an attribute value is changed - * + * * @param saveOnChange * @throws UnsupportedOperationException in case the operation is not supported */ diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuDescriptor.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuDescriptor.java new file mode 100644 index 00000000000..f496277fe09 --- /dev/null +++ b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuDescriptor.java @@ -0,0 +1,164 @@ +package de.metas.handlingunits.material.interceptor; + +import java.math.BigDecimal; +import java.util.List; + +import org.adempiere.mm.attributes.api.AttributesKeys; +import org.adempiere.mm.attributes.api.IAttributeSet; +import org.adempiere.mm.attributes.api.IAttributeSetInstanceBL; +import org.adempiere.mm.attributes.api.ImmutableAttributeSet; +import org.adempiere.util.Services; +import org.adempiere.util.lang.IPair; +import org.adempiere.util.lang.ImmutablePair; +import org.compiere.model.I_M_Attribute; +import org.compiere.model.I_M_AttributeSetInstance; +import org.compiere.model.I_M_InOutLine; +import org.compiere.model.I_M_InventoryLine; +import org.compiere.model.I_M_MovementLine; +import org.eevolution.model.I_PP_Cost_Collector; + +import com.google.common.collect.ImmutableList; + +import de.metas.handlingunits.IHUAssignmentDAO; +import de.metas.handlingunits.IHUAssignmentDAO.HuAssignment; +import de.metas.handlingunits.IHUContextFactory; +import de.metas.handlingunits.IMutableHUContext; +import de.metas.handlingunits.attribute.storage.IAttributeStorage; +import de.metas.handlingunits.model.I_M_HU; +import de.metas.handlingunits.storage.IHUProductStorage; +import de.metas.handlingunits.storage.IHUStorage; +import de.metas.material.event.commons.AttributesKey; +import de.metas.material.event.commons.HUDescriptor; +import de.metas.material.event.commons.HUDescriptor.HUDescriptorBuilder; +import de.metas.material.event.commons.ProductDescriptor; +import lombok.NonNull; + +/* + * #%L + * de.metas.handlingunits.base + * %% + * Copyright (C) 2018 metas GmbH + * %% + * 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 2 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 + * . + * #L% + */ + +public class M_Transaction_HuDescriptor +{ + public static final M_Transaction_HuDescriptor INSTANCE = new M_Transaction_HuDescriptor(); + + private M_Transaction_HuDescriptor() + { + } + + public ImmutableList createHuDescriptorsForInOutLine( + @NonNull final I_M_InOutLine inOutLine, + final boolean deleted) + { + return createHUDescriptorsForModel(inOutLine, deleted); + } + + public ImmutableList createHuDescriptorsForCostCollector( + @NonNull final I_PP_Cost_Collector costCollector, + final boolean deleted) + { + return createHUDescriptorsForModel(costCollector, deleted); + } + + public ImmutableList createHuDescriptorsForMovementLine( + @NonNull final I_M_MovementLine movementLine, + final boolean deleted) + { + return createHUDescriptorsForModel(movementLine, deleted); + } + + public ImmutableList createHuDescriptorsForInventoryLine( + @NonNull final I_M_InventoryLine inventoryLine, + final boolean deleted) + { + return createHUDescriptorsForModel(inventoryLine, deleted); + } + + private static ImmutableList createHUDescriptorsForModel( + @NonNull final Object huReferencedModel, + final boolean deleted) + { + final IHUAssignmentDAO huAssignmentDAO = Services.get(IHUAssignmentDAO.class); + final List huAssignments = huAssignmentDAO + .retrieveHUAssignmentPojosForModel(huReferencedModel); + + final ImmutableList.Builder result = ImmutableList.builder(); + for (final HuAssignment huAssignment : huAssignments) + { + result.addAll(createHuDescriptors(huAssignment.getLowestLevelHU(), deleted)); + } + + return result.build(); + } + + private static ImmutableList createHuDescriptors( + @NonNull final I_M_HU hu, + final boolean deleted) + { + final HUDescriptorBuilder builder = HUDescriptor.builder() + .huId(hu.getM_HU_ID()); + + final IMutableHUContext huContext = Services.get(IHUContextFactory.class).createMutableHUContext(); + final IHUStorage storage = huContext.getHUStorageFactory().getStorage(hu); + + // note that we could have the AttributesKey without making an ASI, but we need the ASI-ID for display reasons in the material dispo window. + final IPair attributesKeyAndAsiId = createAttributesKeyAndAsiId(hu); + + final List productStorages = storage.getProductStorages(); + final ImmutableList.Builder descriptors = ImmutableList.builder(); + for (final IHUProductStorage productStorage : productStorages) + { + final ProductDescriptor productDescriptor = ProductDescriptor + .forProductAndAttributes( + productStorage.getM_Product_ID(), + attributesKeyAndAsiId.getLeft(), + attributesKeyAndAsiId.getRight()); + + final BigDecimal quantity = productStorage.getQtyInStockingUOM(); + + final HUDescriptor descriptor = builder + .productDescriptor(productDescriptor) + .quantity(deleted ? BigDecimal.ZERO : quantity) + .quantityDelta(deleted ? quantity.negate() : quantity) + .build(); + descriptors.add(descriptor); + } + return descriptors.build(); + } + + private static IPair createAttributesKeyAndAsiId(@NonNull final I_M_HU hu) + { + final IMutableHUContext huContext = Services.get(IHUContextFactory.class).createMutableHUContext(); + final IAttributeStorage attributeStorage = huContext.getHUAttributeStorageFactory().getAttributeStorage(hu); + + // we don't want all the non-storage-relevant attributes to pollute the ASI we will display in the material disposition window + final IAttributeSet storageRelevantSubSet = ImmutableAttributeSet.createSubSet(attributeStorage, I_M_Attribute::isStorageRelevant); + + final IAttributeSetInstanceBL attributeSetInstanceBL = Services.get(IAttributeSetInstanceBL.class); + final I_M_AttributeSetInstance asi = attributeSetInstanceBL.createASIFromAttributeSet(storageRelevantSubSet); + + final AttributesKey attributesKey = AttributesKeys + .createAttributesKeyFromASIStorageAttributes(asi.getM_AttributeSetInstance_ID()) + .orElse(AttributesKey.NONE); + + return ImmutablePair.of(attributesKey, asi.getM_AttributeSetInstance_ID()); + } + +} diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuOnHandQtyChangeDescriptor.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuOnHandQtyChangeDescriptor.java deleted file mode 100644 index 620ee19a755..00000000000 --- a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuOnHandQtyChangeDescriptor.java +++ /dev/null @@ -1,129 +0,0 @@ -package de.metas.handlingunits.material.interceptor; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -import org.adempiere.util.Services; -import org.compiere.model.I_M_InOutLine; -import org.compiere.model.I_M_InventoryLine; -import org.compiere.model.I_M_MovementLine; -import org.eevolution.model.I_PP_Cost_Collector; - -import com.google.common.collect.ImmutableList; - -import de.metas.handlingunits.IHUAssignmentDAO; -import de.metas.handlingunits.IHUAssignmentDAO.HuAssignment; -import de.metas.handlingunits.IHandlingUnitsBL; -import de.metas.handlingunits.model.I_M_HU; -import de.metas.handlingunits.storage.IHUProductStorage; -import de.metas.handlingunits.storage.IHUStorage; -import de.metas.handlingunits.storage.IHUStorageFactory; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor.HUOnHandQtyChangeDescriptorBuilder; -import lombok.NonNull; - -/* - * #%L - * de.metas.handlingunits.base - * %% - * Copyright (C) 2018 metas GmbH - * %% - * 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 2 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 - * . - * #L% - */ - -public class M_Transaction_HuOnHandQtyChangeDescriptor -{ - public static final M_Transaction_HuOnHandQtyChangeDescriptor INSTANCE = new M_Transaction_HuOnHandQtyChangeDescriptor(); - - private M_Transaction_HuOnHandQtyChangeDescriptor() - { - } - - public List createHuDescriptorsForInOutLine( - @NonNull final I_M_InOutLine inOutLine, - final boolean deleted) - { - return createHUDescriptorsForModel(inOutLine, deleted); - } - - public List createHuDescriptorsForCostCollector( - @NonNull final I_PP_Cost_Collector costCollector, - final boolean deleted) - { - return createHUDescriptorsForModel(costCollector, deleted); - } - - public List createHuDescriptorsForMovementLine( - @NonNull final I_M_MovementLine movementLine, - final boolean deleted) - { - return createHUDescriptorsForModel(movementLine, deleted); - } - - public List createHuDescriptorsForInventoryLine( - @NonNull final I_M_InventoryLine inventoryLine, - final boolean deleted) - { - return createHUDescriptorsForModel(inventoryLine, deleted); - } - - private static List createHUDescriptorsForModel( - @NonNull final Object huReferencedModel, - final boolean deleted) - { - final IHUAssignmentDAO huAssignmentDAO = Services.get(IHUAssignmentDAO.class); - final List huAssignments = huAssignmentDAO - .retrieveHUAssignmentPojosForModel(huReferencedModel); - - final ImmutableList.Builder result = ImmutableList.builder(); - for (final HuAssignment huAssignment : huAssignments) - { - result.addAll(createHuDescriptors(huAssignment.getLowestLevelHU(), deleted)); - } - - return result.build(); - } - - private static ArrayList createHuDescriptors( - @NonNull final I_M_HU hu, - final boolean deleted) - { - final HUOnHandQtyChangeDescriptorBuilder builder = HUOnHandQtyChangeDescriptor.builder() - .huId(hu.getM_HU_ID()); - - final IHandlingUnitsBL handlingUnitsBL = Services.get(IHandlingUnitsBL.class); - final IHUStorageFactory storageFactory = handlingUnitsBL.getStorageFactory(); - - final IHUStorage storage = storageFactory.getStorage(hu); - final List productStorages = storage.getProductStorages(); - - final ArrayList events = new ArrayList<>(); - - for (final IHUProductStorage productStorage : productStorages) - { - final BigDecimal quantity = productStorage.getQtyInStockingUOM(); - - final HUOnHandQtyChangeDescriptor event = builder - .quantity(deleted ? BigDecimal.ZERO : quantity) - .quantityDelta(deleted ? quantity.negate() : quantity) - .build(); - events.add(event); - } - return events; - } - -} diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreator.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreator.java index 87f3205ee40..502ffe7fff9 100644 --- a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreator.java +++ b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreator.java @@ -3,8 +3,10 @@ import static org.adempiere.model.InterfaceWrapperHelper.load; import java.math.BigDecimal; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.TreeMap; import org.adempiere.ad.dao.IQueryBL; @@ -19,12 +21,17 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimaps; import de.metas.handlingunits.model.I_M_ShipmentSchedule_QtyPicked; import de.metas.handlingunits.movement.api.IHUMovementBL; import de.metas.material.event.MaterialEvent; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; +import de.metas.material.event.commons.HUDescriptor; import de.metas.material.event.commons.MaterialDescriptor; +import de.metas.material.event.commons.ProductDescriptor; import de.metas.material.event.transactions.AbstractTransactionEvent; import de.metas.material.event.transactions.TransactionCreatedEvent; import de.metas.material.event.transactions.TransactionDeletedEvent; @@ -69,19 +76,19 @@ public final List createEventsForTransaction( if (transaction.getInoutLineId() > 0) { - result.add(createEventForInOutLine(transaction, deleted)); + result.addAll(createEventsForInOutLine(transaction, deleted)); } else if (transaction.getCostCollectorId() > 0) { - result.add(createEventForCostCollector(transaction, deleted)); + result.addAll(createEventForCostCollector(transaction, deleted)); } else if (transaction.getMovementLineId() > 0) { - result.add(createEventForMovementLine(transaction, deleted)); + result.addAll(createEventForMovementLine(transaction, deleted)); } else if (transaction.getInventoryLineId() > 0) { - result.add(createEventForInventoryLine(transaction, deleted)); + result.addAll(createEventForInventoryLine(transaction, deleted)); } return result.build(); } @@ -92,71 +99,63 @@ private static boolean isDirectMovementWarehouse(final int warehouseId) return intValue == warehouseId; } - private MaterialEvent createEventForCostCollector( + private List createEventForCostCollector( @NonNull final TransactionDescriptor transaction, final boolean deleted) { - final MaterialDescriptor materialDescriptor = createMaterialDescriptor( - transaction, - transaction.getMovementQty()); - - final boolean directMovementWarehouse = isDirectMovementWarehouse(transaction.getWarehouseId()); + final I_PP_Cost_Collector costCollector = load(transaction.getCostCollectorId(), I_PP_Cost_Collector.class); - final AbstractTransactionEvent event; + final List huDescriptors = // + M_Transaction_HuDescriptor.INSTANCE.createHuDescriptorsForCostCollector(costCollector, deleted); - final I_PP_Cost_Collector costCollector = load(transaction.getCostCollectorId(), I_PP_Cost_Collector.class); + final Map> materialDescriptors = createMaterialDescriptors( + transaction, + costCollector.getPP_Order().getC_BPartner_ID(), + huDescriptors); - final List huDescriptors = // - M_Transaction_HuOnHandQtyChangeDescriptor.INSTANCE.createHuDescriptorsForCostCollector(costCollector, deleted); + final boolean directMovementWarehouse = isDirectMovementWarehouse(transaction.getWarehouseId()); - if (deleted) + final ImmutableList.Builder events = ImmutableList.builder(); + for (final Entry> materialDescriptor : materialDescriptors.entrySet()) { - event = TransactionDeletedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .directMovementWarehouse(directMovementWarehouse) - .ppOrderId(costCollector.getPP_Order_ID()) - .ppOrderLineId(costCollector.getPP_Order_BOMLine_ID()) - .huOnHandQtyChangeDescriptors(huDescriptors) - .build(); - } - else - { - event = TransactionCreatedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .directMovementWarehouse(directMovementWarehouse) - .ppOrderId(costCollector.getPP_Order_ID()) - .ppOrderLineId(costCollector.getPP_Order_BOMLine_ID()) - .huOnHandQtyChangeDescriptors(huDescriptors) - .build(); + final AbstractTransactionEvent event; + if (deleted) + { + event = TransactionDeletedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .directMovementWarehouse(directMovementWarehouse) + .ppOrderId(costCollector.getPP_Order_ID()) + .ppOrderLineId(costCollector.getPP_Order_BOMLine_ID()) + .build(); + } + else + { + event = TransactionCreatedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .directMovementWarehouse(directMovementWarehouse) + .ppOrderId(costCollector.getPP_Order_ID()) + .ppOrderLineId(costCollector.getPP_Order_BOMLine_ID()) + .build(); + } + events.add(event); } - return event; + return events.build(); } - private static MaterialDescriptor createMaterialDescriptor( - @NonNull final TransactionDescriptor transaction, - @NonNull final BigDecimal quantity) - {//TODO move down - return MaterialDescriptor.builder() - .warehouseId(transaction.getWarehouseId()) - .date(transaction.getMovementDate()) - .productDescriptor(transaction.getProductDescriptor()) - .bPartnerId(transaction.getBPartnerId()) - .quantity(quantity) - .build(); - } - - private MaterialEvent createEventForInOutLine( + private List createEventsForInOutLine( @NonNull final TransactionDescriptor transaction, final boolean deleted) { final Map shipmentScheduleIds2Qtys = retrieveShipmentScheduleId2Qty(transaction); - final AbstractTransactionEvent event = createEventForShipmentScheduleToQtyMapping(transaction, shipmentScheduleIds2Qtys, deleted); - return event; + final List events = createEventForShipmentScheduleToQtyMapping(transaction, shipmentScheduleIds2Qtys, deleted); + return events; } @VisibleForTesting @@ -219,134 +218,184 @@ private static void assertSignumsOfQuantitiesMatch( .setParameter("transaction", transaction); } - private static AbstractTransactionEvent createEventForShipmentScheduleToQtyMapping( + private static List createEventForShipmentScheduleToQtyMapping( @NonNull final TransactionDescriptor transaction, @NonNull final Map shipmentScheduleIds2Qtys, final boolean deleted) { final boolean directMovementWarehouse = isDirectMovementWarehouse(transaction.getWarehouseId()); - final MaterialDescriptor materialDescriptor = createMaterialDescriptor( - transaction, - transaction.getMovementQty()); - final I_M_InOutLine inOutLine = load(transaction.getInoutLineId(), I_M_InOutLine.class); - final List huDescriptor = // - M_Transaction_HuOnHandQtyChangeDescriptor.INSTANCE.createHuDescriptorsForInOutLine(inOutLine, deleted); + final List huDescriptors = // + M_Transaction_HuDescriptor.INSTANCE.createHuDescriptorsForInOutLine(inOutLine, deleted); - final AbstractTransactionEvent event; - if (deleted) - { - event = TransactionDeletedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .shipmentScheduleIds2Qtys(shipmentScheduleIds2Qtys) - .directMovementWarehouse(directMovementWarehouse) - .huOnHandQtyChangeDescriptors(huDescriptor) - .build(); - } - else + final Map> materialDescriptors = createMaterialDescriptors( + transaction, + inOutLine.getM_InOut().getC_BPartner_ID(), + huDescriptors); + + final ImmutableList.Builder events = ImmutableList.builder(); + for (final Entry> materialDescriptor : materialDescriptors.entrySet()) { - event = TransactionCreatedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .shipmentScheduleIds2Qtys(shipmentScheduleIds2Qtys) - .directMovementWarehouse(directMovementWarehouse) - .huOnHandQtyChangeDescriptors(huDescriptor) - .build(); + final AbstractTransactionEvent event; + if (deleted) + { + event = TransactionDeletedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .shipmentScheduleIds2Qtys(shipmentScheduleIds2Qtys) + .directMovementWarehouse(directMovementWarehouse) + .build(); + } + else + { + event = TransactionCreatedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .shipmentScheduleIds2Qtys(shipmentScheduleIds2Qtys) + .directMovementWarehouse(directMovementWarehouse) + .build(); + } + events.add(event); } - return event; + return events.build(); } - private MaterialEvent createEventForMovementLine( + private List createEventForMovementLine( @NonNull final TransactionDescriptor transaction, final boolean deleted) { final boolean directMovementWarehouse = isDirectMovementWarehouse(transaction.getWarehouseId()); - final MaterialDescriptor materialDescriptor = createMaterialDescriptor( - transaction, - transaction.getMovementQty()); - - final AbstractTransactionEvent event; final I_M_MovementLine movementLine = load(transaction.getMovementLineId(), I_M_MovementLine.class); + final List huDescriptors = // + M_Transaction_HuDescriptor.INSTANCE.createHuDescriptorsForMovementLine(movementLine, deleted); + + final Map> materialDescriptors = createMaterialDescriptors( + transaction, + movementLine.getM_Movement().getC_BPartner_ID(), + huDescriptors); + final int ddOrderId = movementLine.getDD_OrderLine_ID() > 0 ? movementLine.getDD_OrderLine().getDD_Order_ID() : 0; - final List huDescriptors = // - M_Transaction_HuOnHandQtyChangeDescriptor.INSTANCE.createHuDescriptorsForMovementLine(movementLine, deleted); - - if (deleted) - { - event = TransactionDeletedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .directMovementWarehouse(directMovementWarehouse) - .ddOrderId(ddOrderId) - .ddOrderLineId(movementLine.getDD_OrderLine_ID()) - .huOnHandQtyChangeDescriptors(huDescriptors) - .build(); - } - else + final ImmutableList.Builder events = ImmutableList.builder(); + for (final Entry> materialDescriptor : materialDescriptors.entrySet()) { - event = TransactionCreatedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .directMovementWarehouse(directMovementWarehouse) - .ddOrderId(ddOrderId) - .ddOrderLineId(movementLine.getDD_OrderLine_ID()) - .huOnHandQtyChangeDescriptors(huDescriptors) - .build(); + final AbstractTransactionEvent event; + if (deleted) + { + event = TransactionDeletedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .directMovementWarehouse(directMovementWarehouse) + .ddOrderId(ddOrderId) + .ddOrderLineId(movementLine.getDD_OrderLine_ID()) + .build(); + } + else + { + event = TransactionCreatedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .directMovementWarehouse(directMovementWarehouse) + .ddOrderId(ddOrderId) + .ddOrderLineId(movementLine.getDD_OrderLine_ID()) + .build(); + } + events.add(event); } - - return event; + return events.build(); } - private MaterialEvent createEventForInventoryLine( + private List createEventForInventoryLine( @NonNull final TransactionDescriptor transaction, final boolean deleted) { final boolean directMovementWarehouse = isDirectMovementWarehouse(transaction.getWarehouseId()); - final MaterialDescriptor materialDescriptor = createMaterialDescriptor( - transaction, - transaction.getMovementQty()); - final I_M_InventoryLine inventoryLine = load(transaction.getInventoryLineId(), I_M_InventoryLine.class); - final List huDescriptors = // - M_Transaction_HuOnHandQtyChangeDescriptor.INSTANCE.createHuDescriptorsForInventoryLine(inventoryLine, deleted); + final List huDescriptors = // + M_Transaction_HuDescriptor.INSTANCE.createHuDescriptorsForInventoryLine(inventoryLine, deleted); - final AbstractTransactionEvent event; + final Map> materialDescriptors = createMaterialDescriptors( + transaction, + 0, // bpartnerId + huDescriptors); - if (deleted) + final ImmutableList.Builder events = ImmutableList.builder(); + for (final Entry> materialDescriptor : materialDescriptors.entrySet()) { - event = TransactionDeletedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .directMovementWarehouse(directMovementWarehouse) - .huOnHandQtyChangeDescriptors(huDescriptors) - .build(); + + final AbstractTransactionEvent event; + if (deleted) + { + event = TransactionDeletedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .directMovementWarehouse(directMovementWarehouse) + .build(); + } + else + { + event = TransactionCreatedEvent.builder() + .eventDescriptor(transaction.getEventDescriptor()) + .transactionId(transaction.getTransactionId()) + .materialDescriptor(materialDescriptor.getKey()) + .huOnHandQtyChangeDescriptors(materialDescriptor.getValue()) + .directMovementWarehouse(directMovementWarehouse) + .build(); + } + events.add(event); } - else + return events.build(); + } + + @VisibleForTesting + static Map> createMaterialDescriptors( + @NonNull final TransactionDescriptor transaction, + final int bPartnerId, + @NonNull final Collection huDescriptors) + { + final ImmutableListMultimap productDescriptor2huDescriptor = // + Multimaps.index(huDescriptors, HUDescriptor::getProductDescriptor); + + final ImmutableSet>> entrySet = productDescriptor2huDescriptor.asMap().entrySet(); + + final ImmutableMap.Builder> result = ImmutableMap.builder(); + + for (final Entry> entry : entrySet) { - event = TransactionCreatedEvent.builder() - .eventDescriptor(transaction.getEventDescriptor()) - .transactionId(transaction.getTransactionId()) - .materialDescriptor(materialDescriptor) - .directMovementWarehouse(directMovementWarehouse) - .huOnHandQtyChangeDescriptors(huDescriptors) + final BigDecimal quantity = entry.getValue() + .stream() + .map(HUDescriptor::getQuantity) + .map(qty -> transaction.getMovementQty().signum() >= 0 ? qty : qty.negate()) // set signum according to transaction.movementQty + .reduce(BigDecimal.ZERO, BigDecimal::add); + + final MaterialDescriptor materialDescriptor = MaterialDescriptor.builder() + .warehouseId(transaction.getWarehouseId()) + .date(transaction.getMovementDate()) + .productDescriptor(entry.getKey()) + .bPartnerId(bPartnerId) + .quantity(quantity) .build(); - } - return event; + result.put(materialDescriptor, entry.getValue()); + } + return result.build(); } } diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/TransactionDescriptor.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/TransactionDescriptor.java index ed0993653c4..bb45781b363 100644 --- a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/TransactionDescriptor.java +++ b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/material/interceptor/TransactionDescriptor.java @@ -3,14 +3,11 @@ import java.math.BigDecimal; import java.util.Date; -import org.compiere.Adempiere; import org.compiere.model.I_M_Transaction; import com.google.common.annotations.VisibleForTesting; -import de.metas.material.event.ModelProductDescriptorExtractor; import de.metas.material.event.commons.EventDescriptor; -import de.metas.material.event.commons.ProductDescriptor; import lombok.NonNull; import lombok.Value; @@ -37,27 +34,22 @@ */ /** - * This pojo contains everything needed to create events for {@link I_M_Transaction}s. + * This pojo contains properties needed to create events for {@link I_M_Transaction}s. * We need it because we need to construct the events *after* commit, in order to also catch assigned HUs. */ @Value public class TransactionDescriptor { - @SuppressWarnings("deprecation") @VisibleForTesting public static TransactionDescriptor ofRecord(@NonNull final I_M_Transaction record) { - final ModelProductDescriptorExtractor productDescriptorFactory = Adempiere.getBean(ModelProductDescriptorExtractor.class); - final ProductDescriptor productDescriptor = productDescriptorFactory.createProductDescriptor(record); - return new TransactionDescriptor( EventDescriptor.createNew(record), - productDescriptor, + record.getM_Product_ID(), record.getM_Transaction_ID(), record.getM_Locator().getM_Warehouse_ID(), record.getMovementDate(), record.getMovementQty(), - record.getC_BPartner_ID(), record.getPP_Cost_Collector_ID(), record.getM_InOutLine_ID(), record.getM_MovementLine_ID(), @@ -66,8 +58,8 @@ public static TransactionDescriptor ofRecord(@NonNull final I_M_Transaction reco } EventDescriptor eventDescriptor; - ProductDescriptor productDescriptor; + int productId; int transactionId; int warehouseId; int costCollectorId; @@ -75,18 +67,16 @@ public static TransactionDescriptor ofRecord(@NonNull final I_M_Transaction reco int movementLineId; int inventoryLineId; Date movementDate; - int bPartnerId; BigDecimal movementQty; String movementType; private TransactionDescriptor( EventDescriptor eventDescriptor, - ProductDescriptor productDescriptor, + int productId, int transactionId, int warehouseId, Date movementDate, BigDecimal movementQty, - int bPartnerId, int costCollectorId, int inoutLineId, int movementLineId, @@ -94,12 +84,11 @@ private TransactionDescriptor( String movementType) { this.eventDescriptor = eventDescriptor; - this.productDescriptor = productDescriptor; + this.productId = productId; this.transactionId = transactionId; this.warehouseId = warehouseId; this.movementDate = movementDate; this.movementQty = movementQty; - this.bPartnerId = bPartnerId; this.costCollectorId = costCollectorId; this.inoutLineId = inoutLineId; this.movementLineId = movementLineId; diff --git a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/receiptschedule/impl/HUReceiptScheduleBL.java b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/receiptschedule/impl/HUReceiptScheduleBL.java index 98ff4110768..dc56b2c4dbf 100644 --- a/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/receiptschedule/impl/HUReceiptScheduleBL.java +++ b/de.metas.handlingunits.base/src/main/java/de/metas/handlingunits/receiptschedule/impl/HUReceiptScheduleBL.java @@ -40,6 +40,8 @@ import java.util.Properties; import java.util.Set; +import javax.annotation.Nullable; + import org.adempiere.ad.trx.api.ITrx; import org.adempiere.ad.trx.api.ITrxManager; import org.adempiere.ad.trx.api.OnTrxMissingPolicy; @@ -408,12 +410,9 @@ private final InOutGenerateResult processReceiptSchedules0(final Properties ctx, } /** - * - * @param hu - * @param vendorBPartnerId * @task https://github.com/metasfresh/metasfresh-webui/issues/209 */ - private void printReceiptLabel(final HUToReport hu, final int vendorBPartnerId) + private void printReceiptLabel(@Nullable final HUToReport hu, final int vendorBPartnerId) { if (hu == null) { diff --git a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/HUTestHelper.java b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/HUTestHelper.java index 9a7dd2a1567..38b064c842b 100644 --- a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/HUTestHelper.java +++ b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/HUTestHelper.java @@ -1212,13 +1212,6 @@ public I_M_HU_PI_Attribute createM_HU_PI_Attribute(final HUPIAttributeBuilder at return attributeBuilder.create(ctx); } - public void setAttributeValue(final I_M_HU hu, final I_M_Attribute attribute, final Object value) - { - final IAttributeStorageFactory storageFactory = getHUContext().getHUAttributeStorageFactory(); - final IAttributeStorage attributes = storageFactory.getAttributeStorage(hu); - attributes.setValue(attribute, value); - } - public void createAttributeListValues(final org.compiere.model.I_M_Attribute attribute, final String... values) { for (final String value : values) @@ -1465,7 +1458,7 @@ public List retrieveAllHandlingUnits() public List retrieveAllHandlingUnitsOfType(final I_M_HU_PI huPI) { final IHandlingUnitsBL handlingUnitsBL = Services.get(IHandlingUnitsBL.class); - + final List result = new ArrayList<>(); // Filter diff --git a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagationTest.java b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagationTest.java index 3b32b109a64..5e7c98b736e 100644 --- a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagationTest.java +++ b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagationTest.java @@ -10,12 +10,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 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 * . @@ -40,7 +40,6 @@ import de.metas.handlingunits.AbstractHUTest; import de.metas.handlingunits.HUAssert; import de.metas.handlingunits.HUTestHelper; -import de.metas.handlingunits.HUXmlConverter; import de.metas.handlingunits.IHandlingUnitsBL; import de.metas.handlingunits.IHandlingUnitsDAO; import de.metas.handlingunits.attribute.IAttributeValue; @@ -72,7 +71,7 @@ public class AttributesPropagationTest extends AbstractHUTest protected void initialize() { attributesTestHelper = new AttributesTestHelper(); - + // // Handling Units Definition huDefBag = helper.createHUDefinition(HUTestHelper.NAME_Bag_Product, X_M_HU_PI_Version.HU_UNITTYPE_TransportUnit); @@ -103,7 +102,7 @@ protected void initialize() //helper.createHU_PI_Item_IncludedHU(huDefIFCO, huDefIFCO, BigDecimal.ONE); final I_M_HU_PI_Item piTU_Item_IFCO = helper.createHU_PI_Item_Material(huDefIFCO); helper.assignProduct(piTU_Item_IFCO, pTomato, BigDecimal.TEN, uomEach); - + // value will not be propagated helper.createM_HU_PI_Attribute(new HUPIAttributeBuilder(attr_FragileSticker) .setM_HU_PI(huDefIFCO) @@ -149,7 +148,7 @@ private List createIncomingPalets() // create and destroy instances only with a I_M_Transaction final List huPalets = helper.createHUsFromSimplePI(incomingTrxDoc, huDefPalet); - + return huPalets; } @@ -157,8 +156,8 @@ private List createIncomingPalets() public void testNormalPropagation() { final List huPalets = createIncomingPalets(); - System.out.println(HUXmlConverter.toString(HUXmlConverter.toXml("huPalets", huPalets))); - + //System.out.println(HUXmlConverter.toString(HUXmlConverter.toXml("huPalets", huPalets))); + Assert.assertEquals("There should be 2 palets", 2, huPalets.size()); final I_M_HU palet1 = huPalets.get(0); diff --git a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagation_1Palet_2IFCO_Test.java b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagation_1Palet_2IFCO_Test.java index 28bbce84f9c..b40e5cc0359 100644 --- a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagation_1Palet_2IFCO_Test.java +++ b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/AttributesPropagation_1Palet_2IFCO_Test.java @@ -94,8 +94,6 @@ private void setupHU_PI_Attribute(final I_M_Attribute attribute, final Class splitStrategyClass, final Class aggregationStrategyClass) { - // final Class splitStrategyClass = NullSplitterStrategy.class; - // final Class aggregationStrategyClass = SumAggregationStrategy.class; helper.createM_HU_PI_Attribute(new HUPIAttributeBuilder(attribute) .setM_HU_PI(huDefIFCO) .setPropagationType(propagationType) diff --git a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/WeightAttributeValueCalloutTest.java b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/WeightAttributeValueCalloutTest.java index d8ca68ddba3..2bd246bd3fa 100644 --- a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/WeightAttributeValueCalloutTest.java +++ b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/attributes/impl/WeightAttributeValueCalloutTest.java @@ -10,12 +10,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 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 * . @@ -108,7 +108,6 @@ protected void initialize() { final I_M_HU_PI_Item itemMA = helper.createHU_PI_Item_Material(huDefIFCO); helper.assignProduct(itemMA, pTomato, COUNT_TOMATOS_PER_IFCO, uomEach); - helper.createHU_PI_Item_PackingMaterial(huDefIFCO, pmIFCO); } @@ -117,7 +116,6 @@ protected void initialize() huDefPalet = helper.createHUDefinition(HUTestHelper.NAME_Palet_Product, X_M_HU_PI_Version.HU_UNITTYPE_LoadLogistiqueUnit); { helper.createHU_PI_Item_IncludedHU(huDefPalet, huDefIFCO, COUNT_IFCOS_PER_PALET); - helper.createHU_PI_Item_PackingMaterial(huDefPalet, pmPallets); } } diff --git a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuDescriptorTest.java b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuDescriptorTest.java new file mode 100644 index 00000000000..c9122de77be --- /dev/null +++ b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_HuDescriptorTest.java @@ -0,0 +1,134 @@ +package de.metas.handlingunits.material.interceptor; + +import static org.adempiere.model.InterfaceWrapperHelper.newInstance; +import static org.adempiere.model.InterfaceWrapperHelper.save; +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.util.List; + +import org.adempiere.ad.trx.api.ITrx; +import org.adempiere.mm.attributes.api.IAttributeDAO; +import org.adempiere.util.Services; +import org.compiere.model.I_M_AttributeValue; +import org.compiere.model.I_M_InOutLine; +import org.compiere.model.I_M_Transaction; +import org.compiere.model.X_M_Transaction; +import org.junit.Before; +import org.junit.Test; + +import de.metas.handlingunits.HUTestHelper; +import de.metas.handlingunits.IHUAssignmentBL; +import de.metas.handlingunits.attribute.storage.IAttributeStorage; +import de.metas.handlingunits.attribute.storage.IAttributeStorageFactory; +import de.metas.handlingunits.model.I_M_HU; +import de.metas.handlingunits.model.I_M_HU_PI; +import de.metas.handlingunits.model.I_M_HU_PI_Item; +import de.metas.handlingunits.model.X_M_HU_PI_Version; +import de.metas.material.event.commons.AttributesKey; +import de.metas.material.event.commons.HUDescriptor; +import de.metas.material.event.commons.ProductDescriptor; + +/* + * #%L + * de.metas.handlingunits.base + * %% + * Copyright (C) 2018 metas GmbH + * %% + * 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 2 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 + * . + * #L% + */ + +public class M_Transaction_HuDescriptorTest +{ + private static final BigDecimal THIRTY_IFCOS_PER_PALET = new BigDecimal("30"); + private static final BigDecimal FOURTY_TOMATOES_PER_IFCO = new BigDecimal("40"); + private static final BigDecimal TOTAL_CU_QTY = FOURTY_TOMATOES_PER_IFCO.multiply(THIRTY_IFCOS_PER_PALET); + + private I_M_HU_PI huDefPalet; + + private HUTestHelper helper; + + @Before + public void init() + { + helper = new HUTestHelper(); + + // HU PI: IFCO + final I_M_HU_PI huDefIFCO = helper.createHUDefinition(HUTestHelper.NAME_IFCO_Product, X_M_HU_PI_Version.HU_UNITTYPE_TransportUnit); + final I_M_HU_PI_Item itemMA = helper.createHU_PI_Item_Material(huDefIFCO); + helper.assignProduct(itemMA, helper.pTomato, FOURTY_TOMATOES_PER_IFCO, helper.uomEach); + helper.createHU_PI_Item_PackingMaterial(huDefIFCO, helper.pmIFCO); + + // HU PI: Palet + huDefPalet = helper.createHUDefinition(HUTestHelper.NAME_Palet_Product, X_M_HU_PI_Version.HU_UNITTYPE_LoadLogistiqueUnit); + helper.createHU_PI_Item_IncludedHU(huDefPalet, huDefIFCO, THIRTY_IFCOS_PER_PALET); + helper.createHU_PI_Item_PackingMaterial(huDefPalet, helper.pmPalet); + + helper.attr_CountryMadeIn.setIsStorageRelevant(true); + save(helper.attr_CountryMadeIn); + } + + @Test + public void createHuDescriptorsForInOutLine() + { + // create an inoutline and a transaction + final I_M_InOutLine inOutLine = newInstance(I_M_InOutLine.class); + save(inOutLine); + + final I_M_Transaction transaction = helper.createMTransaction( + X_M_Transaction.MOVEMENTTYPE_VendorReceipts, + helper.pTomato, + TOTAL_CU_QTY); + transaction.setM_InOutLine(inOutLine); + save(transaction); + + // create a palet and assign it to our inout line + final List huPalets = helper.createHUsFromSimplePI(transaction, huDefPalet); + assertThat(huPalets).hasSize(1); + + Services.get(IHUAssignmentBL.class).assignHU(inOutLine, huPalets.get(0), ITrx.TRXNAME_ThreadInherited); + + final IAttributeStorageFactory attributeStorageFactory = helper.getHUContext().getHUAttributeStorageFactory(); + final IAttributeStorage attributeStorage = attributeStorageFactory.getAttributeStorage(huPalets.get(0)); + attributeStorage.setValue(helper.attr_CountryMadeIn, HUTestHelper.COUNTRYMADEIN_RO); + attributeStorage.saveChangesIfNeeded(); + + // retrieve our countryMadeIn attribute-value and make sure that the AttributeKeys tool will be able to work with it + final I_M_AttributeValue attributeValue = Services.get(IAttributeDAO.class).retrieveAttributeValueOrNull( + helper.attr_CountryMadeIn, + HUTestHelper.COUNTRYMADEIN_RO); + assertThat(attributeValue).isNotNull(); + assertThat(attributeValue.getM_AttributeValue_ID()).isGreaterThan(0); + + // + // invoke the method under test + final List huDescriptorsForInOutLine = M_Transaction_HuDescriptor.INSTANCE + .createHuDescriptorsForInOutLine(inOutLine, false); + + assertThat(huDescriptorsForInOutLine).hasSize(1); + final HUDescriptor huDescriptor = huDescriptorsForInOutLine.get(0); + + assertThat(huDescriptor.getHuId()).isEqualTo(huPalets.get(0).getM_HU_ID()); // within the palet, everything is homogenous, that's why we expect the palet's ID (and not e.g. the IFCO's). + assertThat(huDescriptor.getQuantity()).isEqualByComparingTo(TOTAL_CU_QTY); + assertThat(huDescriptor.getQuantityDelta()).isEqualByComparingTo(TOTAL_CU_QTY); + + final ProductDescriptor productDescriptor = huDescriptor.getProductDescriptor(); + + assertThat(productDescriptor.getProductId()).isEqualTo(helper.pTomato.getM_Product_ID()); + assertThat(productDescriptor.getStorageAttributesKey()).isNotEqualTo(AttributesKey.NONE); + assertThat(productDescriptor.getStorageAttributesKey().getAttributeValueIds()).containsOnly(attributeValue.getM_AttributeValue_ID()); + } +} diff --git a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreatorTest.java b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreatorTest.java index 4fa1db47d70..ebde74c602a 100644 --- a/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreatorTest.java +++ b/de.metas.handlingunits.base/src/test/java/de/metas/handlingunits/material/interceptor/M_Transaction_TransactionEventCreatorTest.java @@ -1,5 +1,7 @@ package de.metas.handlingunits.material.interceptor; +import static java.math.BigDecimal.ONE; +import static java.math.BigDecimal.TEN; import static org.adempiere.model.InterfaceWrapperHelper.newInstance; import static org.adempiere.model.InterfaceWrapperHelper.save; import static org.assertj.core.api.Assertions.assertThat; @@ -7,13 +9,17 @@ import java.math.BigDecimal; import java.sql.Timestamp; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.adempiere.mm.attributes.api.impl.ModelProductDescriptorExtractorUsingAttributeSetInstanceFactory; import org.adempiere.test.AdempiereTestHelper; import org.adempiere.util.time.SystemTime; +import org.compiere.model.I_C_BPartner; +import org.compiere.model.I_M_InOut; import org.compiere.model.I_M_InOutLine; import org.compiere.model.I_M_Locator; import org.compiere.model.I_M_Product; @@ -26,12 +32,21 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import com.google.common.collect.ImmutableList; + import de.metas.ShutdownListener; import de.metas.StartupListener; import de.metas.business.BusinessTestHelper; import de.metas.inoutcandidate.model.I_M_ShipmentSchedule_QtyPicked; import de.metas.material.event.MaterialEvent; +import de.metas.material.event.commons.AttributesKey; +import de.metas.material.event.commons.HUDescriptor; +import de.metas.material.event.commons.MaterialDescriptor; +import de.metas.material.event.commons.ProductDescriptor; import de.metas.material.event.transactions.AbstractTransactionEvent; +import lombok.NonNull; +import mockit.Expectations; +import mockit.Mocked; /* * #%L @@ -61,12 +76,13 @@ public class M_Transaction_TransactionEventCreatorTest { + private static final BigDecimal SEVEN = new BigDecimal("7");; private static final BigDecimal THREE = new BigDecimal("3"); private static final BigDecimal TWO = new BigDecimal("2"); - private static final BigDecimal ONE = BigDecimal.ONE; - private static final BigDecimal MINUS_ONE = ONE.negate(); + private static final BigDecimal MINUS_ONE = new BigDecimal("-1"); private static final BigDecimal MINUS_TWO = new BigDecimal("-2"); - private static final BigDecimal MINUS_TEN = BigDecimal.TEN.negate(); + private static final BigDecimal MINUS_SEVEN = new BigDecimal("-7"); + private static final BigDecimal MINUS_TEN = TEN.negate(); private static final int SOME_OTHER_INOUT_LINE_ID = 30; @@ -76,6 +92,9 @@ public class M_Transaction_TransactionEventCreatorTest private Timestamp movementDate; private I_M_InOutLine inoutLine; + @Mocked + M_Transaction_HuDescriptor huDescriptorCreator; + @Before public void init() { @@ -86,8 +105,16 @@ public void init() product = BusinessTestHelper.createProduct("product", BusinessTestHelper.createUomEach()); movementDate = SystemTime.asTimestamp(); + final I_C_BPartner bPartner = newInstance(I_C_BPartner.class); + save(bPartner); + + final I_M_InOut inout = newInstance(I_M_InOut.class); + inout.setC_BPartner(bPartner); + save(inout); + inoutLine = newInstance(I_M_InOutLine.class); inoutLine.setM_Product(product); + inoutLine.setM_InOut(inout); save(inoutLine); } @@ -97,6 +124,8 @@ public void createEventsForTransaction_shipment_but_not_shipmentSchedule() { final I_M_Transaction transaction = createShipmentTransaction(); + setupSingleHuDescriptor(SEVEN); + // invoke the method under test final List events = M_Transaction_TransactionEventCreator.INSTANCE .createEventsForTransaction(TransactionDescriptor.ofRecord(transaction), false); @@ -104,7 +133,7 @@ public void createEventsForTransaction_shipment_but_not_shipmentSchedule() final AbstractTransactionEvent event = (AbstractTransactionEvent)events.get(0); assertCommon(transaction, event); - assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_TEN); + assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_SEVEN); assertThat(event.getShipmentScheduleIds2Qtys()).hasSize(0); } @@ -114,20 +143,22 @@ public void createEventsForTransaction_single_shipmentSchedule() final I_M_ShipmentSchedule_QtyPicked shipmentScheduleQtyPicked = newInstance(I_M_ShipmentSchedule_QtyPicked.class); shipmentScheduleQtyPicked.setM_ShipmentSchedule_ID(20); shipmentScheduleQtyPicked.setM_InOutLine(inoutLine); - shipmentScheduleQtyPicked.setQtyPicked(BigDecimal.TEN); + shipmentScheduleQtyPicked.setQtyPicked(TEN); save(shipmentScheduleQtyPicked); final I_M_Transaction transaction = createShipmentTransaction(); transaction.setM_InOutLine(inoutLine); save(transaction); + setupSingleHuDescriptor(SEVEN); + // invoke the method under test final List events = M_Transaction_TransactionEventCreator.INSTANCE .createEventsForTransaction(TransactionDescriptor.ofRecord(transaction), false); final AbstractTransactionEvent event = (AbstractTransactionEvent)events.get(0); assertCommon(transaction, event); - assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_TEN); + assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_SEVEN); assertThat(event.getShipmentScheduleIds2Qtys()).hasSize(1); final Entry entry = event.getShipmentScheduleIds2Qtys().entrySet().iterator().next(); @@ -148,6 +179,9 @@ public void createEventsForTransaction_single_shipmentSchedule_with_partial_quan transaction.setM_InOutLine(inoutLine); save(transaction); + setupSingleHuDescriptor(SEVEN); + + // // invoke the method under test final List events = M_Transaction_TransactionEventCreator.INSTANCE .createEventsForTransaction(TransactionDescriptor.ofRecord(transaction), false); @@ -156,13 +190,35 @@ public void createEventsForTransaction_single_shipmentSchedule_with_partial_quan final AbstractTransactionEvent event = (AbstractTransactionEvent)events.get(0); assertCommon(transaction, event); - assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_TEN); + assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_SEVEN); // the HUs' qty takes precendence of the transaction's movementQty final Map shipmentScheduleIds2Qtys = event.getShipmentScheduleIds2Qtys(); assertThat(shipmentScheduleIds2Qtys).containsOnly(entry(20, MINUS_ONE)); } + private void setupSingleHuDescriptor( + @NonNull final BigDecimal huQty) + { + final ProductDescriptor productDescriptor = ProductDescriptor.forProductAndAttributes( + product.getM_Product_ID(), + AttributesKey.NONE); + + final HUDescriptor huDescriptor = HUDescriptor.builder() + .huId(10) + .productDescriptor(productDescriptor) + .quantity(huQty) + .quantityDelta(huQty) + .build(); + + // @formatter:off + new Expectations() + {{ + huDescriptorCreator.createHuDescriptorsForInOutLine(inoutLine, false); + result = ImmutableList.of(huDescriptor); + }}; // @formatter:on + } + @Test public void createEventsForTransaction_multiple_shipmentSchedules_with_partial_quantity() { @@ -189,6 +245,8 @@ public void createEventsForTransaction_multiple_shipmentSchedules_with_partial_q transaction.setM_InOutLine(inoutLine); save(transaction); + setupSingleHuDescriptor(SEVEN); + // invoke the method under test final List events = M_Transaction_TransactionEventCreator.INSTANCE .createEventsForTransaction(TransactionDescriptor.ofRecord(transaction), false); @@ -196,7 +254,7 @@ public void createEventsForTransaction_multiple_shipmentSchedules_with_partial_q assertThat(events).hasSize(1); final AbstractTransactionEvent event = (AbstractTransactionEvent)events.get(0); assertCommon(transaction, event); - assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_TEN); + assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(MINUS_SEVEN); final Map shipmentScheduleIds2Qtys = event.getShipmentScheduleIds2Qtys(); assertThat(shipmentScheduleIds2Qtys).containsOnly( @@ -205,14 +263,44 @@ public void createEventsForTransaction_multiple_shipmentSchedules_with_partial_q } private I_M_Transaction createShipmentTransaction() + { + return createTransaction(X_M_Transaction.MOVEMENTTYPE_CustomerShipment, MINUS_TEN); + } + + @Test + public void createEventsForTransaction_receipt() + { + final I_M_Transaction transaction = createReceiptTransaction(); + + setupSingleHuDescriptor(SEVEN); + + // invoke the method under test + final List events = M_Transaction_TransactionEventCreator.INSTANCE + .createEventsForTransaction(TransactionDescriptor.ofRecord(transaction), false); + assertThat(events).hasSize(1); + + final AbstractTransactionEvent event = (AbstractTransactionEvent)events.get(0); + assertCommon(transaction, event); + assertThat(event.getMaterialDescriptor().getQuantity()).isEqualByComparingTo(SEVEN); + assertThat(event.getShipmentScheduleIds2Qtys()).hasSize(0); + } + + private I_M_Transaction createReceiptTransaction() + { + return createTransaction(X_M_Transaction.MOVEMENTTYPE_VendorReceipts, TEN); + } + + private I_M_Transaction createTransaction( + @NonNull final String movementType, + @NonNull final BigDecimal movementQty) { final I_M_Transaction transaction = newInstance(I_M_Transaction.class); transaction.setM_Locator(locator); - transaction.setMovementType(X_M_Transaction.MOVEMENTTYPE_CustomerShipment); + transaction.setMovementType(movementType); transaction.setM_Product(product); transaction.setMovementDate(movementDate); transaction.setM_InOutLine(inoutLine); - transaction.setMovementQty(MINUS_TEN); + transaction.setMovementQty(movementQty); save(transaction); return transaction; } @@ -225,4 +313,100 @@ private void assertCommon(final I_M_Transaction transaction, final AbstractTrans assertThat(result.getMaterialDescriptor().getProductId()).isEqualTo(product.getM_Product_ID()); assertThat(result.getMaterialDescriptor().getDate()).isEqualTo(movementDate); } + + @Test + public void createMaterialDescriptors_HUs_with_different_attributes() + { + final ProductDescriptor productDescriptor1 = ProductDescriptor.forProductAndAttributes(product.getM_Product_ID(), AttributesKey.NONE); + final HUDescriptor huDescriptor1 = HUDescriptor.builder() + .huId(20) + .productDescriptor(productDescriptor1) + .quantity(SEVEN) + .quantityDelta(SEVEN) + .build(); + + final AttributesKey attributesKey = AttributesKey.ofAttributeValueIds(10, 20); + final ProductDescriptor productDescriptor2 = ProductDescriptor.forProductAndAttributes(product.getM_Product_ID(), attributesKey); + final HUDescriptor huDescriptor2 = HUDescriptor.builder() + .huId(20) + .productDescriptor(productDescriptor2) + .quantity(THREE) + .quantityDelta(THREE) + .build(); + + final I_M_Transaction transaction = createReceiptTransaction(); + final TransactionDescriptor transactionDescriptor = TransactionDescriptor.ofRecord(transaction); + + // + // invoke the method under test + final Map> materialDescriptors = M_Transaction_TransactionEventCreator + .createMaterialDescriptors( + transactionDescriptor, + 0, // bPartnerId + ImmutableList.of(huDescriptor1, huDescriptor2)); + + final Set>> entrySet = materialDescriptors.entrySet(); + assertThat(entrySet).hasSize(2); + + assertThat(entrySet) + .filteredOn(e -> e.getKey().getStorageAttributesKey().equals(AttributesKey.NONE)) + .hasSize(1) + .allSatisfy(singleEntry -> { + assertThat(singleEntry.getKey().getProductId()).isEqualTo(product.getM_Product_ID()); + assertThat(singleEntry.getKey().getQuantity()).isEqualByComparingTo(SEVEN); + assertThat(singleEntry.getValue()).containsExactly(huDescriptor1); + }); + + assertThat(entrySet) + .filteredOn(e -> e.getKey().getStorageAttributesKey().equals(attributesKey)) + .hasSize(1) + .allSatisfy(singleEntry -> { + assertThat(singleEntry.getKey().getProductId()).isEqualTo(product.getM_Product_ID()); + assertThat(singleEntry.getKey().getQuantity()).isEqualByComparingTo(THREE); + assertThat(singleEntry.getValue()).containsExactly(huDescriptor2); + }); + } + + @Test + public void createMaterialDescriptors_HUs_with_equal_attributes() + { + final AttributesKey attributesKeys = AttributesKey.ofAttributeValueIds(10, 20); + + final ProductDescriptor productDescriptor1 = ProductDescriptor.forProductAndAttributes(product.getM_Product_ID(), attributesKeys); + final HUDescriptor huDescriptor1 = HUDescriptor.builder() + .huId(20) + .productDescriptor(productDescriptor1) + .quantity(SEVEN) + .quantityDelta(SEVEN) + .build(); + + final ProductDescriptor productDescriptor2 = ProductDescriptor.forProductAndAttributes(product.getM_Product_ID(), attributesKeys); + final HUDescriptor huDescriptor2 = HUDescriptor.builder() + .huId(20) + .productDescriptor(productDescriptor2) + .quantity(THREE) + .quantityDelta(THREE) + .build(); + + final I_M_Transaction transaction = createReceiptTransaction(); + final TransactionDescriptor transactionDescriptor = TransactionDescriptor.ofRecord(transaction); + + // + // invoke the method under test + final Map> materialDescriptors = M_Transaction_TransactionEventCreator + .createMaterialDescriptors( + transactionDescriptor, + 0, // bpartnerId + ImmutableList.of(huDescriptor1, huDescriptor2)); + + final Set>> entrySet = materialDescriptors.entrySet(); + assertThat(entrySet).hasSize(1); + + final Entry> singleEntry = entrySet.iterator().next(); + assertThat(singleEntry.getKey().getProductId()).isEqualTo(product.getM_Product_ID()); + assertThat(singleEntry.getKey().getStorageAttributesKey()).isEqualTo(attributesKeys); + assertThat(singleEntry.getKey().getQuantity()).isEqualByComparingTo(TEN); + assertThat(singleEntry.getValue()).hasSize(2); + assertThat(singleEntry.getValue()).containsExactlyInAnyOrder(huDescriptor1, huDescriptor2); + } } diff --git a/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/RepositoryCommons.java b/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/RepositoryCommons.java index fde2b3ef52e..9c087cbd2c4 100644 --- a/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/RepositoryCommons.java +++ b/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/RepositoryCommons.java @@ -1,5 +1,6 @@ package de.metas.material.dispo.commons.repository; +import java.util.List; import java.util.Objects; import javax.annotation.Nullable; @@ -67,7 +68,7 @@ public IQueryBuilder mkQueryBuilder(@NonNull final CandidatesQue final IQueryBuilder builder = queryBL.createQueryBuilder(I_MD_Candidate.class) .addOnlyActiveRecordsFilter(); - if(CandidatesQuery.FALSE.equals(query)) + if (CandidatesQuery.FALSE.equals(query)) { builder.filter(ConstantQueryFilter.of(false)); return builder; @@ -83,7 +84,6 @@ else if (query.getId() > 0) builder.addEqualsFilter(I_MD_Candidate.COLUMN_MD_Candidate_Type, query.getType().toString()); } - if (query.getParentId() >= 0) { builder.addEqualsFilter(I_MD_Candidate.COLUMN_MD_Candidate_Parent_ID, query.getParentId()); @@ -299,8 +299,8 @@ private void addTransactionDetailToFilter( @NonNull final CandidatesQuery query, @NonNull final IQueryBuilder builder) { - final TransactionDetail transactionDetail = query.getTransactionDetail(); - if (transactionDetail == null) + final List transactionDetails = query.getTransactionDetails(); + if (transactionDetails == null || transactionDetails.isEmpty()) { return; } @@ -311,18 +311,20 @@ private void addTransactionDetailToFilter( .createQueryBuilder(I_MD_Candidate_Transaction_Detail.class) .addOnlyActiveRecordsFilter(); - Preconditions.checkArgument( - transactionDetail.getTransactionId() > 0, - "Every transactionDetail instance needs to have transactionId>0; transactionDetail=%s", - transactionDetail); - transactionDetailSubQueryBuilder.addEqualsFilter(I_MD_Candidate_Transaction_Detail.COLUMN_M_Transaction_ID, transactionDetail.getTransactionId()); - - if (transactionDetail.getQuantity() != null) + for (final TransactionDetail transactionDetail : transactionDetails) { - transactionDetailSubQueryBuilder.addEqualsFilter(I_MD_Candidate_Transaction_Detail.COLUMN_MovementQty, transactionDetail.getQuantity()); - } + Preconditions.checkArgument( + transactionDetail.getTransactionId() > 0, + "Every transactionDetail instance needs to have transactionId>0; transactionDetail=%s", + transactionDetail); + transactionDetailSubQueryBuilder.addEqualsFilter(I_MD_Candidate_Transaction_Detail.COLUMN_M_Transaction_ID, transactionDetail.getTransactionId()); - builder.addInSubQueryFilter(I_MD_Candidate.COLUMN_MD_Candidate_ID, I_MD_Candidate_Transaction_Detail.COLUMN_MD_Candidate_ID, transactionDetailSubQueryBuilder.create()); + if (transactionDetail.getQuantity() != null) + { + transactionDetailSubQueryBuilder.addEqualsFilter(I_MD_Candidate_Transaction_Detail.COLUMN_MovementQty, transactionDetail.getQuantity()); + } + builder.addInSubQueryFilter(I_MD_Candidate.COLUMN_MD_Candidate_ID, I_MD_Candidate_Transaction_Detail.COLUMN_MD_Candidate_ID, transactionDetailSubQueryBuilder.create()); + } } public T retrieveSingleCandidateDetail( diff --git a/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/query/CandidatesQuery.java b/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/query/CandidatesQuery.java index 46f41c6bab1..6d669d45542 100644 --- a/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/query/CandidatesQuery.java +++ b/de.metas.material/dispo-commons/src/main/java/de/metas/material/dispo/commons/repository/query/CandidatesQuery.java @@ -1,5 +1,6 @@ package de.metas.material.dispo.commons.repository.query; +import java.util.List; import java.util.Objects; import org.adempiere.util.Check; @@ -15,6 +16,7 @@ import de.metas.material.dispo.commons.repository.MaterialDescriptorQuery; import lombok.Builder; import lombok.NonNull; +import lombok.Singular; import lombok.Value; import lombok.experimental.Wither; @@ -85,6 +87,7 @@ public static CandidatesQuery fromCandidate( .matchExactStorageAttributesKey(true) .demandDetail(candidate.getDemandDetail()) .distributionDetailsQuery(distributionDetailsQuery) + .transactionDetails(candidate.getTransactionDetails()) .groupId(candidate.getGroupId()) .orgId(candidate.getOrgId()) .productionDetailsQuery(productionDetailsQuery) @@ -158,7 +161,10 @@ public static CandidatesQuery fromId(final int id) */ DemandDetail demandDetail; - TransactionDetail transactionDetail; + /** + * If multiple transactionDetails are specified here, then a matching candidate needs to have matching transactionDetails for all of them. + */ + List transactionDetails; @Builder public CandidatesQuery( @@ -176,7 +182,7 @@ public CandidatesQuery( final ProductionDetailsQuery productionDetailsQuery, final DistributionDetailsQuery distributionDetailsQuery, final DemandDetail demandDetail, - final TransactionDetail transactionDetail) + @Singular final List transactionDetails) { this.parentMaterialDescriptorQuery = parentMaterialDescriptorQuery; this.parentDemandDetail = parentDemandDetail; @@ -194,7 +200,7 @@ public CandidatesQuery( this.productionDetailsQuery = productionDetailsQuery; this.distributionDetailsQuery = distributionDetailsQuery; this.demandDetail = demandDetail; - this.transactionDetail = transactionDetail; + this.transactionDetails = transactionDetails; } /** diff --git a/de.metas.material/dispo-service/src/main/java/de/metas/material/dispo/service/event/handler/TransactionEventHandler.java b/de.metas.material/dispo-service/src/main/java/de/metas/material/dispo/service/event/handler/TransactionEventHandler.java index fd3485d9695..db1544d45cb 100644 --- a/de.metas.material/dispo-service/src/main/java/de/metas/material/dispo/service/event/handler/TransactionEventHandler.java +++ b/de.metas.material/dispo-service/src/main/java/de/metas/material/dispo/service/event/handler/TransactionEventHandler.java @@ -3,9 +3,11 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeSet; import org.adempiere.exceptions.AdempiereException; import org.adempiere.util.Loggables; @@ -26,14 +28,13 @@ import de.metas.material.dispo.commons.candidate.ProductionDetail.Flag; import de.metas.material.dispo.commons.candidate.TransactionDetail; import de.metas.material.dispo.commons.repository.CandidateRepositoryRetrieval; -import de.metas.material.dispo.commons.repository.MaterialDescriptorQuery; import de.metas.material.dispo.commons.repository.query.CandidatesQuery; import de.metas.material.dispo.commons.repository.query.DistributionDetailsQuery; import de.metas.material.dispo.commons.repository.query.ProductionDetailsQuery; import de.metas.material.dispo.service.candidatechange.CandidateChangeService; import de.metas.material.event.MaterialEventHandler; import de.metas.material.event.PostMaterialEventService; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; +import de.metas.material.event.commons.HUDescriptor; import de.metas.material.event.picking.PickingRequestedEvent; import de.metas.material.event.transactions.AbstractTransactionEvent; import de.metas.material.event.transactions.TransactionCreatedEvent; @@ -154,7 +155,7 @@ private void firePickRequiredEvent( return; } - final List huOnHandQtyChangeDescriptors = transactionEvent.getHuOnHandQtyChangeDescriptors(); + final Collection huOnHandQtyChangeDescriptors = transactionEvent.getHuOnHandQtyChangeDescriptors(); final boolean noHUsToPick = huOnHandQtyChangeDescriptors == null || huOnHandQtyChangeDescriptors.isEmpty(); if (noHUsToPick) { @@ -164,7 +165,7 @@ private void firePickRequiredEvent( final ImmutableList huIdsToPick = huOnHandQtyChangeDescriptors.stream() .filter(huDescriptor -> huDescriptor.getQuantity().signum() > 0) - .map(HUOnHandQtyChangeDescriptor::getHuId) + .map(HUDescriptor::getHuId) .collect(ImmutableList.toImmutableList()); final PickingRequestedEvent pickingRequestedEvent = PickingRequestedEvent.builder() @@ -221,7 +222,7 @@ private Candidate createCandidateForShipmentSchedule( } else if (existingCandidate != null) { - candidate = newCandidateWithAddedTransactionDetail( + candidate = createCandidateWithchangedTransactionDetailAndQuantity( existingCandidate, createTransactionDetail(event)); } @@ -271,7 +272,7 @@ private Candidate createCandidateForPPorder(@NonNull final AbstractTransactionEv } else if (existingCandidate != null) { - candidate = newCandidateWithAddedTransactionDetail( + candidate = createCandidateWithchangedTransactionDetailAndQuantity( existingCandidate, transactionDetailOfEvent); } @@ -315,7 +316,7 @@ private Candidate createCandidateForDDorder(@NonNull final AbstractTransactionEv } else if (existingCandidate != null) { - candidate = newCandidateWithAddedTransactionDetail( + candidate = createCandidateWithchangedTransactionDetailAndQuantity( existingCandidate, transactionDetailOfEvent); } @@ -348,7 +349,6 @@ private Candidate prepareUnrelatedCandidate(@NonNull final AbstractTransactionEv final TransactionDetail transactionDetailOfEvent = createTransactionDetail(event); final CandidatesQuery query = CandidatesQuery.builder() - .materialDescriptorQuery(MaterialDescriptorQuery.forDescriptor(event.getMaterialDescriptor())) .transactionDetail(TransactionDetail.forQuery(event.getTransactionId())) .build(); final Candidate existingCandidate = candidateRepository.retrieveLatestMatchOrNull(query); @@ -364,7 +364,7 @@ private Candidate prepareUnrelatedCandidate(@NonNull final AbstractTransactionEv } else if (existingCandidate != null) { - candidate = newCandidateWithAddedTransactionDetailAndQuantity( + candidate = createCandidateWithchangedTransactionDetailAndQuantity( existingCandidate, transactionDetailOfEvent); } @@ -375,29 +375,22 @@ else if (existingCandidate != null) return candidate; } - private Candidate newCandidateWithAddedTransactionDetailAndQuantity( + private Candidate createCandidateWithchangedTransactionDetailAndQuantity( @NonNull final Candidate candidate, - @NonNull final TransactionDetail transactionDetail) + @NonNull final TransactionDetail changedTransactionDetail) { - final BigDecimal newQuantity = candidate - .getQuantity() - .add(transactionDetail.getQuantity()); + // note: make sure we don't end up with duplicated transactionDetails - Candidate newCandidate = candidate.withQuantity(newQuantity); - newCandidate = newCandidateWithAddedTransactionDetail(newCandidate, transactionDetail); - return newCandidate; - } + final ImmutableList otherTransactionDetails = candidate.getTransactionDetails() + .stream() + .filter(transactionDetail -> transactionDetail.getTransactionId() != changedTransactionDetail.getTransactionId()) + .collect(ImmutableList.toImmutableList()); - private Candidate newCandidateWithAddedTransactionDetail( - @NonNull final Candidate candidate, - @NonNull final TransactionDetail transactionDetail) - { - final Builder newTransactionDetailsList = // - ImmutableList. builder() - .addAll(candidate.getTransactionDetails()) - .add(transactionDetail); + final TreeSet newTransactionDetailsSet = new TreeSet<>(Comparator.comparing(TransactionDetail::getTransactionId)); + newTransactionDetailsSet.add(changedTransactionDetail); + newTransactionDetailsSet.addAll(otherTransactionDetails); - final Candidate withTransactionDetails = candidate.withTransactionDetails(newTransactionDetailsList.build()); + final Candidate withTransactionDetails = candidate.withTransactionDetails(ImmutableList.copyOf(newTransactionDetailsSet)); final BigDecimal actualQty = withTransactionDetails.computeActualQty(); final BigDecimal plannedQty = candidate.getPlannedQty(); diff --git a/de.metas.material/dispo-service/src/main/sql/postgresql/system/5491890_sys_gh3935-app_MD_Candidate_drop_ASI-FK-constraint.sql b/de.metas.material/dispo-service/src/main/sql/postgresql/system/5491890_sys_gh3935-app_MD_Candidate_drop_ASI-FK-constraint.sql new file mode 100644 index 00000000000..57ff8bf3185 --- /dev/null +++ b/de.metas.material/dispo-service/src/main/sql/postgresql/system/5491890_sys_gh3935-app_MD_Candidate_drop_ASI-FK-constraint.sql @@ -0,0 +1,11 @@ +-- 2018-04-25T12:47:38.950 +-- I forgot to set the DICTIONARY_ID_COMMENTS System Configurator +UPDATE AD_Column SET DDL_NoForeignKey='Y',Updated=TO_TIMESTAMP('2018-04-25 12:47:38','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=556509 +; + +ALTER TABLE public.md_candidate DROP CONSTRAINT mattributesetinstance_mdcandid; + +-- 2018-04-25T16:25:17.417 +-- I forgot to set the DICTIONARY_ID_COMMENTS System Configurator +UPDATE AD_Column SET TechnicalNote='We have no FK-constraint, because this column is only here to be displayed (the real stuff is StorageAttributesKey).',Updated=TO_TIMESTAMP('2018-04-25 16:25:17','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=556509 +; diff --git a/de.metas.material/dispo-service/src/test/java/de/metas/material/dispo/service/event/handler/TransactionCreatedHandlerTests.java b/de.metas.material/dispo-service/src/test/java/de/metas/material/dispo/service/event/handler/TransactionCreatedHandlerTests.java index 8618eda321d..2e9bd0076eb 100644 --- a/de.metas.material/dispo-service/src/test/java/de/metas/material/dispo/service/event/handler/TransactionCreatedHandlerTests.java +++ b/de.metas.material/dispo-service/src/test/java/de/metas/material/dispo/service/event/handler/TransactionCreatedHandlerTests.java @@ -133,7 +133,8 @@ public void createCandidate_unrelated_transaction_no_existing_candiate() CandidatesQuery query; candidateRepository.retrieveLatestMatchOrNull(query = withCapture()); assertThat(query).isNotNull(); - assertThat(query.getTransactionDetail().getTransactionId()).isEqualTo(TRANSACTION_ID); + assertThat(query.getTransactionDetails()).hasSize(1); + assertThat(query.getTransactionDetails().get(0).getTransactionId()).isEqualTo(TRANSACTION_ID); }}; // @formatter:on assertThat(candidate.getType()).isEqualTo(CandidateType.UNRELATED_INCREASE); @@ -177,7 +178,8 @@ public void createCandidate_unrelated_transaction_already_existing_candiate_with CandidatesQuery query; candidateRepository.retrieveLatestMatchOrNull(query = withCapture()); assertThat(query).isNotNull(); - assertThat(query.getTransactionDetail().getTransactionId()).isEqualTo(TRANSACTION_ID); + assertThat(query.getTransactionDetails()).hasSize(1); + assertThat(query.getTransactionDetails().get(0).getTransactionId()).isEqualTo(TRANSACTION_ID); }}; // @formatter:on assertThat(candidate.getType()).isEqualTo(CandidateType.UNRELATED_INCREASE); @@ -300,7 +302,7 @@ private static void assertDemandDetailQuery(final CandidatesQuery query) assertThat(query.getMaterialDescriptorQuery()) .as("If we have a demand detail, then only query via that demand detail") .isNull(); - assertThat(query.getTransactionDetail()).as("only search via the demand detail, if we have one").isNull(); + assertThat(query.getTransactionDetails()).as("only search via the demand detail, if we have one").isEmpty(); } private TransactionCreatedEventBuilder createTransactionEventBuilderWithQuantity(@NonNull final BigDecimal quantity) diff --git a/de.metas.material/event/src/main/java/de/metas/material/event/commons/HUOnHandQtyChangeDescriptor.java b/de.metas.material/event/src/main/java/de/metas/material/event/commons/HUDescriptor.java similarity index 62% rename from de.metas.material/event/src/main/java/de/metas/material/event/commons/HUOnHandQtyChangeDescriptor.java rename to de.metas.material/event/src/main/java/de/metas/material/event/commons/HUDescriptor.java index f10de362ea7..67900c2c5f2 100644 --- a/de.metas.material/event/src/main/java/de/metas/material/event/commons/HUOnHandQtyChangeDescriptor.java +++ b/de.metas.material/event/src/main/java/de/metas/material/event/commons/HUDescriptor.java @@ -6,7 +6,11 @@ import org.adempiere.util.Check; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + import lombok.Builder; +import lombok.NonNull; import lombok.Value; /* @@ -32,21 +36,30 @@ */ @Value -@Builder(toBuilder = true) -public class HUOnHandQtyChangeDescriptor +public class HUDescriptor { BigDecimal quantity; BigDecimal quantityDelta; + ProductDescriptor productDescriptor; + int huId; - public void assertValid() + @JsonCreator + @Builder(toBuilder = true) + private HUDescriptor( + @JsonProperty("huId") final int huId, + @JsonProperty("productDescriptor") @NonNull final ProductDescriptor productDescriptor, + @JsonProperty("quantity") @NonNull final BigDecimal quantity, + @JsonProperty("quantityDelta") @NonNull final BigDecimal quantityDelta) { - checkIdGreaterThanZero("huId", huId); + this.huId = checkIdGreaterThanZero("huId", huId); + this.productDescriptor = productDescriptor; - Check.errorIf(quantity == null, "quantity may not be null"); Check.errorIf(quantity.signum() < 0, "quantity may not be less than zero"); - Check.errorIf(quantityDelta == null, "quantityDelta may not be null"); + this.quantity = quantity; + + this.quantityDelta = quantityDelta; } } diff --git a/de.metas.material/event/src/main/java/de/metas/material/event/transactions/AbstractTransactionEvent.java b/de.metas.material/event/src/main/java/de/metas/material/event/transactions/AbstractTransactionEvent.java index a3829e3f177..d3403616f2c 100644 --- a/de.metas.material/event/src/main/java/de/metas/material/event/transactions/AbstractTransactionEvent.java +++ b/de.metas.material/event/src/main/java/de/metas/material/event/transactions/AbstractTransactionEvent.java @@ -3,7 +3,7 @@ import static de.metas.material.event.MaterialEventUtils.checkIdGreaterThanZero; import java.math.BigDecimal; -import java.util.List; +import java.util.Collection; import java.util.Map; import javax.annotation.OverridingMethodsMustInvokeSuper; @@ -12,7 +12,7 @@ import de.metas.material.event.MaterialEvent; import de.metas.material.event.commons.EventDescriptor; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; +import de.metas.material.event.commons.HUDescriptor; import de.metas.material.event.commons.MaterialDescriptor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -63,7 +63,7 @@ public abstract class AbstractTransactionEvent implements MaterialEvent private final int ddOrderLineId; - private final List huOnHandQtyChangeDescriptors; + private final Collection huOnHandQtyChangeDescriptors; public AbstractTransactionEvent( final EventDescriptor eventDescriptor, @@ -75,7 +75,7 @@ public AbstractTransactionEvent( final int ddOrderLineId, final int transactionId, final boolean directMovementWarehouse, - final List huOnHandQtyChangeDescriptors) + final Collection huOnHandQtyChangeDescriptors) { this.transactionId = checkIdGreaterThanZero("transactionId", transactionId); @@ -105,7 +105,5 @@ public void assertValid() Check.errorIf(materialDescriptor == null, "materialDescriptor may not be null"); materialDescriptor.asssertMaterialDescriptorComplete(); - - huOnHandQtyChangeDescriptors.forEach(HUOnHandQtyChangeDescriptor::assertValid); } } diff --git a/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionCreatedEvent.java b/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionCreatedEvent.java index 2e87fead399..1f4c3f3f740 100644 --- a/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionCreatedEvent.java +++ b/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionCreatedEvent.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import de.metas.material.event.commons.EventDescriptor; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; +import de.metas.material.event.commons.HUDescriptor; import de.metas.material.event.commons.MaterialDescriptor; import lombok.Builder; import lombok.Singular; @@ -51,7 +51,7 @@ private TransactionCreatedEvent( @JsonProperty("ddOrderLineId") final int ddOrderLineId, @JsonProperty("transactionId") final int transactionId, @JsonProperty("directMovementWarehouse") final boolean directMovementWarehouse, - @JsonProperty("huOnHandQtyChangeDescriptor") @Singular final List huOnHandQtyChangeDescriptors) + @JsonProperty("huOnHandQtyChangeDescriptor") @Singular final List huOnHandQtyChangeDescriptors) { super(eventDescriptor, materialDescriptor, diff --git a/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionDeletedEvent.java b/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionDeletedEvent.java index ba371c090be..13f1b34b1b1 100644 --- a/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionDeletedEvent.java +++ b/de.metas.material/event/src/main/java/de/metas/material/event/transactions/TransactionDeletedEvent.java @@ -1,14 +1,14 @@ package de.metas.material.event.transactions; import java.math.BigDecimal; -import java.util.List; +import java.util.Collection; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import de.metas.material.event.commons.EventDescriptor; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; +import de.metas.material.event.commons.HUDescriptor; import de.metas.material.event.commons.MaterialDescriptor; import lombok.Builder; import lombok.Singular; @@ -51,7 +51,7 @@ public TransactionDeletedEvent( @JsonProperty("ddOrderLineId") final int ddOrderLineId, @JsonProperty("transactionId") final int transactionId, @JsonProperty("directMovementWarehouse") final boolean directMovementWarehouse, - @JsonProperty("huOnHandQtyChangeDescriptor")final List huOnHandQtyChangeDescriptors) + @JsonProperty("huOnHandQtyChangeDescriptor")final Collection huOnHandQtyChangeDescriptors) { super(eventDescriptor, materialDescriptor, diff --git a/de.metas.material/event/src/test/java/de/metas/material/event/MaterialEventSerializerTests.java b/de.metas.material/event/src/test/java/de/metas/material/event/MaterialEventSerializerTests.java index da1b1f129ad..dbdf3bf3b53 100644 --- a/de.metas.material/event/src/test/java/de/metas/material/event/MaterialEventSerializerTests.java +++ b/de.metas.material/event/src/test/java/de/metas/material/event/MaterialEventSerializerTests.java @@ -19,7 +19,7 @@ import de.metas.event.SimpleObjectSerializer; import de.metas.material.event.commons.EventDescriptor; -import de.metas.material.event.commons.HUOnHandQtyChangeDescriptor; +import de.metas.material.event.commons.HUDescriptor; import de.metas.material.event.commons.MaterialDescriptor; import de.metas.material.event.commons.OrderLineDescriptor; import de.metas.material.event.commons.SubscriptionLineDescriptor; @@ -556,8 +556,9 @@ public static TransactionCreatedEvent createSampleTransactionEvent() .materialDescriptor(createMaterialDescriptor()) .shipmentScheduleIds2Qty(20, TEN) .shipmentScheduleIds2Qty(21, ONE.negate()) - .huOnHandQtyChangeDescriptor(HUOnHandQtyChangeDescriptor.builder() + .huOnHandQtyChangeDescriptor(HUDescriptor.builder() .huId(30) + .productDescriptor(createProductDescriptor()) .quantity(TEN) .quantityDelta(ONE) .build())