diff --git a/src/integration-test/java/org/openlmis/stockmanagement/service/StockEventValidationsServiceIntegrationTest.java b/src/integration-test/java/org/openlmis/stockmanagement/service/StockEventValidationsServiceIntegrationTest.java index 66abb4e8..d972141a 100644 --- a/src/integration-test/java/org/openlmis/stockmanagement/service/StockEventValidationsServiceIntegrationTest.java +++ b/src/integration-test/java/org/openlmis/stockmanagement/service/StockEventValidationsServiceIntegrationTest.java @@ -40,6 +40,7 @@ import org.openlmis.stockmanagement.validators.DefaultAdjustmentReasonValidator; import org.openlmis.stockmanagement.validators.DefaultFreeTextValidator; import org.openlmis.stockmanagement.validators.DefaultUnpackKitValidator; +import org.openlmis.stockmanagement.validators.DuplicateTransactionValidator; import org.openlmis.stockmanagement.validators.LotValidator; import org.openlmis.stockmanagement.validators.MandatoryFieldsValidator; import org.openlmis.stockmanagement.validators.OrderableLotDuplicationValidator; @@ -104,6 +105,9 @@ public class StockEventValidationsServiceIntegrationTest extends BaseIntegration @MockBean private SourceDestinationGeoLevelAffinityValidator sourceDestinationGeoLeveLAffinityValidator; + @MockBean + private DuplicateTransactionValidator duplicateTransactionValidator; + @MockBean private ExtensionManager extensionManager; @@ -125,6 +129,7 @@ public void setUp() throws Exception { doNothing().when(reasonExistenceValidator).validate(any(StockEventDto.class)); doNothing().when(physicalInventoryReasonsValidator).validate(any(StockEventDto.class)); doNothing().when(unpackKitValidator).validate(any(StockEventDto.class)); + doNothing().when(duplicateTransactionValidator).validate(any(StockEventDto.class)); when(extensionManager .getExtension(ExtensionPointId.ADJUSTMENT_REASON_POINT_ID, AdjustmentReasonValidator.class)) .thenReturn(adjustmentReasonValidator); @@ -159,6 +164,7 @@ public void shouldValidateWithAllImplementationsOfValidators() throws Exception verify(adjustmentReasonValidator, times(1)).validate(stockEventDto); verify(freeTextValidator, times(1)).validate(stockEventDto); verify(unpackKitValidator, times(1)).validate(stockEventDto); + verify(duplicateTransactionValidator, times(1)).validate(stockEventDto); } @Test diff --git a/src/main/java/org/openlmis/stockmanagement/i18n/MessageKeys.java b/src/main/java/org/openlmis/stockmanagement/i18n/MessageKeys.java index 1f63c117..eb04134e 100644 --- a/src/main/java/org/openlmis/stockmanagement/i18n/MessageKeys.java +++ b/src/main/java/org/openlmis/stockmanagement/i18n/MessageKeys.java @@ -208,6 +208,7 @@ public abstract class MessageKeys { + ".orderable.and.lot.duplication"; //stock events creation: lot public static final String ERROR_EVENT_LOT_NOT_EXIST = EVENT_ERROR_PREFIX + ".lot.not.exist"; + public static final String ERROR_EVENT_IS_DUPLICATE = EVENT_ERROR_PREFIX + ".is.duplicate"; public static final String ERROR_EVENT_LOT_ORDERABLE_NOT_MATCH = EVENT_ERROR_PREFIX + ".lot.not.match.orderable"; public static final String ERROR_EVENT_CANNOT_UNPACK_REGULAR_ORDERABLE = EVENT_ERROR_PREFIX diff --git a/src/main/java/org/openlmis/stockmanagement/repository/StockCardLineItemRepository.java b/src/main/java/org/openlmis/stockmanagement/repository/StockCardLineItemRepository.java index 6dcb5e54..961c872a 100644 --- a/src/main/java/org/openlmis/stockmanagement/repository/StockCardLineItemRepository.java +++ b/src/main/java/org/openlmis/stockmanagement/repository/StockCardLineItemRepository.java @@ -16,9 +16,39 @@ package org.openlmis.stockmanagement.repository; import java.util.UUID; +import org.hibernate.jpa.TypedParameterValue; import org.openlmis.stockmanagement.domain.card.StockCardLineItem; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; +@Repository public interface StockCardLineItemRepository - extends PagingAndSortingRepository { + extends PagingAndSortingRepository { + @Query(value = "SELECT EXISTS (SELECT 1 " + + "FROM stockmanagement.stock_card_line_items scli " + + "JOIN stockmanagement.stock_cards sc ON sc.id = scli.stockcardid " + + "WHERE sc.facilityid= :facilityId " + + "AND (cast(:lotId as uuid) IS NULL OR sc.lotId= :lotId) " + + "AND sc.orderableid= :orderableId " + + "AND (cast(:destinationId as uuid) IS NULL OR scli.destinationid= :destinationId) " + + "AND (cast(:sourceId as uuid) IS NULL OR scli.sourceId= :sourceId) " + + "AND scli.occurreddate= CAST(:occurredDate AS DATE) " + + "AND scli.quantity = :quantity " + + "AND (cast(:reasonId as uuid) IS NULL OR scli.reasonid= :reasonId) " + + "AND (cast(:vvmStatus as VARCHAR) " + + "IS NULL OR scli.extradata ->> 'vvmStatus' = :vvmStatus))", + nativeQuery = true) + boolean getByAllGivenFields( + UUID facilityId, + TypedParameterValue lotId, + UUID orderableId, + TypedParameterValue destinationId, + TypedParameterValue sourceId, + String occurredDate, + int quantity, + TypedParameterValue reasonId, + TypedParameterValue vvmStatus + ); + } diff --git a/src/main/java/org/openlmis/stockmanagement/service/StockEventValidationsService.java b/src/main/java/org/openlmis/stockmanagement/service/StockEventValidationsService.java index e482997e..d2d269f7 100644 --- a/src/main/java/org/openlmis/stockmanagement/service/StockEventValidationsService.java +++ b/src/main/java/org/openlmis/stockmanagement/service/StockEventValidationsService.java @@ -22,6 +22,7 @@ import org.openlmis.stockmanagement.extension.point.FreeTextValidator; import org.openlmis.stockmanagement.extension.point.UnpackKitValidator; import org.openlmis.stockmanagement.validators.ApprovedOrderableValidator; +import org.openlmis.stockmanagement.validators.DuplicateTransactionValidator; import org.openlmis.stockmanagement.validators.LotValidator; import org.openlmis.stockmanagement.validators.MandatoryFieldsValidator; import org.openlmis.stockmanagement.validators.OrderableLotDuplicationValidator; @@ -43,6 +44,8 @@ @Service public class StockEventValidationsService { + @Autowired + private DuplicateTransactionValidator duplicateTransactionValidator; @Autowired private ApprovedOrderableValidator approvedOrderableValidator; @@ -85,6 +88,7 @@ public class StockEventValidationsService { * @param stockEventDto the event to be validated. */ public void validate(StockEventDto stockEventDto) { + duplicateTransactionValidator.validate(stockEventDto); approvedOrderableValidator.validate(stockEventDto); lotValidator.validate(stockEventDto); mandatoryFieldsValidator.validate(stockEventDto); diff --git a/src/main/java/org/openlmis/stockmanagement/validators/DuplicateTransactionValidator.java b/src/main/java/org/openlmis/stockmanagement/validators/DuplicateTransactionValidator.java new file mode 100644 index 00000000..b8e3d9f0 --- /dev/null +++ b/src/main/java/org/openlmis/stockmanagement/validators/DuplicateTransactionValidator.java @@ -0,0 +1,104 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program 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, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.stockmanagement.validators; + +import static org.openlmis.stockmanagement.i18n.MessageKeys.ERROR_EVENT_IS_DUPLICATE; + +import java.time.format.DateTimeFormatter; +import java.util.UUID; +import org.hibernate.jpa.TypedParameterValue; +import org.hibernate.type.PostgresUUIDType; +import org.hibernate.type.StringType; +import org.openlmis.stockmanagement.dto.StockEventDto; +import org.openlmis.stockmanagement.dto.StockEventLineItemDto; +import org.openlmis.stockmanagement.exception.ValidationMessageException; +import org.openlmis.stockmanagement.repository.StockCardLineItemRepository; +import org.openlmis.stockmanagement.util.Message; +import org.openlmis.stockmanagement.validators.StockEventValidator; +import org.slf4j.profiler.Profiler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +/** + * This validator ensures that the stock event being sent in is not a duplicate, by + * making sure that all line items are not duplicated from a previous event in the same day. + */ +@Component(value = "DuplicateValidator") +public class DuplicateTransactionValidator implements StockEventValidator { + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + @Autowired + private StockCardLineItemRepository stockCardLineItemRepository; + + @Override + public void validate(StockEventDto stockEventDto) { + XLOGGER.entry(stockEventDto); + Profiler profiler = new Profiler("DUPLICATE_VALIDATOR"); + profiler.setLogger(XLOGGER); + + if (!stockEventDto.hasLineItems()) { + return; + } + validateNotDuplicate(stockEventDto); + + profiler.stop().log(); + XLOGGER.exit(stockEventDto); + } + + private void validateNotDuplicate(StockEventDto stockEventDto) { + int lineItemCount = stockEventDto.getLineItems().size(); + int duplicateCount = 0; + for (StockEventLineItemDto lineItem : stockEventDto.getLineItems()) { + boolean isDuplicate = checkDuplicate(lineItem, stockEventDto.getFacilityId()); + if (isDuplicate) { + duplicateCount += 1; + } + } + System.out.println("number of items: " + lineItemCount); + System.out.println("number of duplicates: " + duplicateCount); + if (duplicateCount == lineItemCount) { + throw new ValidationMessageException( + new Message(ERROR_EVENT_IS_DUPLICATE)); + } + } + + private boolean checkDuplicate( + StockEventLineItemDto stockEventLineItemDto, UUID facilityId) { + TypedParameterValue lotId = new TypedParameterValue( + PostgresUUIDType.INSTANCE, stockEventLineItemDto.getLotId()); + TypedParameterValue destinationId = new TypedParameterValue( + PostgresUUIDType.INSTANCE, stockEventLineItemDto.getDestinationId()); + TypedParameterValue sourceId = new TypedParameterValue( + PostgresUUIDType.INSTANCE, stockEventLineItemDto.getSourceId()); + TypedParameterValue reasonId = new TypedParameterValue( + PostgresUUIDType.INSTANCE, stockEventLineItemDto.getReasonId()); + TypedParameterValue vvmStatus = new TypedParameterValue( + StringType.INSTANCE, stockEventLineItemDto.getExtraData().get("vvmStatus")); + + boolean duplicatesExist = + stockCardLineItemRepository.getByAllGivenFields( + facilityId, lotId, + stockEventLineItemDto.getOrderableId(), + destinationId, sourceId, + stockEventLineItemDto.getOccurredDate().format(formatter), + stockEventLineItemDto.getQuantity(), + reasonId, + vvmStatus); + + return duplicatesExist; + } + +} diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index c6fcf278..b8eb976f 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -53,6 +53,7 @@ stockmanagement.error.event.orderable.not.in.approvedList=The following orderabl stockmanagement.error.event.orderable.disabled.vvm=VVM Status configuration is not enabled for orderable: {0}. #stock event creation: lot stockmanagement.error.event.lot.not.exist=Lot {0} does not exist. +stockmanagement.error.event.is.duplicate=Event is duplicate, please check stock before proceeding. stockmanagement.error.event.lot.not.match.orderable=Lot {0} is not under {1}. #stock event creation: soh #stock card line item reason