Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
8.6.0-SNAPSHOT (WIP)
==================
* [OLMIS-4412](https://openlmis.atlassian.net/browse/OLMIS-4412): Release requisitions with all zero quantities without creating an order.

8.5.1 / 2026-06-09
==================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import guru.nidi.ramltester.RamlDefinition;
import guru.nidi.ramltester.RamlLoaders;
import guru.nidi.ramltester.restassured.RestAssuredClient;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
Expand All @@ -64,6 +65,8 @@
import org.openlmis.requisition.domain.RequisitionTemplateColumnDataBuilder;
import org.openlmis.requisition.domain.requisition.Requisition;
import org.openlmis.requisition.domain.requisition.RequisitionBuilder;
import org.openlmis.requisition.domain.requisition.RequisitionLineItem;
import org.openlmis.requisition.domain.requisition.RequisitionLineItemDataBuilder;
import org.openlmis.requisition.domain.requisition.RequisitionStatus;
import org.openlmis.requisition.dto.FacilityDto;
import org.openlmis.requisition.dto.OrderDto;
Expand All @@ -78,6 +81,7 @@
import org.openlmis.requisition.repository.RequisitionTemplateRepository;
import org.openlmis.requisition.service.fulfillment.OrderFulfillmentService;
import org.openlmis.requisition.service.referencedata.FacilityReferenceDataService;
import org.openlmis.requisition.service.referencedata.OrderableReferenceDataService;
import org.openlmis.requisition.service.referencedata.SupplyLineReferenceDataService;
import org.openlmis.requisition.service.referencedata.UserFulfillmentFacilitiesReferenceDataService;
import org.openlmis.requisition.testutils.DtoGenerator;
Expand Down Expand Up @@ -137,6 +141,9 @@ public class RequisitionServiceIntegrationTest {
@MockBean
private OrderFulfillmentService orderFulfillmentService;

@MockBean
private OrderableReferenceDataService orderableReferenceDataService;

@Autowired
private AvailableRequisitionColumnRepository availableColumnRepository;

Expand Down Expand Up @@ -202,6 +209,9 @@ public void setUp() {
any(UUID.class), eq(right.getId()))).willReturn(managedFacilities);

doNothing().when(orderFulfillmentService).create(ordersCaptor.capture());

given(orderableReferenceDataService.findByIdentities(any()))
.willReturn(new ArrayList<>());
}

@Test
Expand Down Expand Up @@ -279,6 +289,30 @@ public void shouldNotBuildOrdersIfSaveFailed() {
verify(orderFulfillmentService, times(0)).create(anyList());
}

@Test
public void shouldReleaseWithoutOrderWhenAllPacksToShipAreZero() {
final long reqsCountBefore = requisitionRepository.count();
Requisition requisition = mockAndSaveRequisition(generateRequisition(0L));
ReleasableRequisitionBatchDto releaseDto = generateReleaseRequisitionDto(
Collections.singletonList(
getReleasableRequisitionDto(requisition)
));

restAssured.given()
.header(HttpHeaders.AUTHORIZATION, getTokenHeader())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(releaseDto)
.when()
.post(BATCH_RELEASES_URL)
.then()
.statusCode(HttpStatus.CREATED.value());

Requisition released = requisitionRepository.findById(requisition.getId()).orElse(null);
assertEquals(RequisitionStatus.RELEASED_WITHOUT_ORDER, released.getStatus());
verify(orderFulfillmentService, times(0)).create(anyList());
assertEquals(reqsCountBefore + 1, requisitionRepository.count());
}

/**
* https://openlmis.atlassian.net/browse/OLMIS-6876 indicates that there is some exception
* after saving the requisitions and before the return statement of
Expand Down Expand Up @@ -318,6 +352,10 @@ private Requisition saveRequisition(Requisition requisition) {
}

private Requisition generateRequisition() {
return generateRequisition(5L);
}

private Requisition generateRequisition(Long packsToShip) {
Requisition requisition = RequisitionBuilder.newRequisition(UUID.randomUUID(),
UUID.randomUUID(), false);
requisition.setTemplate(template);
Expand All @@ -326,6 +364,13 @@ private Requisition generateRequisition() {
requisition.setStatus(RequisitionStatus.APPROVED);
requisition.setSupervisoryNodeId(supervisoryNodeId);

RequisitionLineItem lineItem = new RequisitionLineItemDataBuilder()
.withRequisition(requisition)
.withPacksToShip(packsToShip)
.withSkippedFlag(false)
.buildAsNew();
requisition.setRequisitionLineItems(new ArrayList<>(Collections.singletonList(lineItem)));

return requisition;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,17 @@ public void reject(Map<VersionIdentityDto, OrderableDto> products, UUID rejector
statusChanges.add(StatusChange.newStatusChange(this, rejector));
}

/**
* Returns true if at least one non-skipped line item has a positive packsToShip value.
* Returns false when all non-skipped line items have zero or null packsToShip,
* meaning the requisition results in a blank order with nothing to ship.
*/
public boolean hasNonZeroPacksToShip() {
return getNonSkippedRequisitionLineItems()
.stream()
.anyMatch(line -> line.getPacksToShip() != null && line.getPacksToShip() > 0L);
}

/**
* Release the requisition.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,14 +553,17 @@ public ValidationResult validateCanSaveRequisition(Requisition requisition) {

/**
* Releases the list of given requisitions as order.
* Requisitions with all zero packsToShip are automatically released without order
* and collected in autoReleasedWithoutOrder instead of the returned list.
*
* @param convertToOrderDtos list of Requisitions with their supplyingDepots to be released as
* order
* @return list of released requisitions
* @param convertToOrderDtos list of Requisitions with their supplyingDepots
* @param autoReleasedWithoutOrder output list populated with requisitions auto-released
* without order (all packsToShip zero or null)
* @return list of requisitions released as order (non-zero packsToShip only)
*/
private List<Requisition> releaseRequisitionsAsOrder(
List<ReleasableRequisitionDto> convertToOrderDtos, UserDto user,
Boolean isLocallyFulfilled) {
Boolean isLocallyFulfilled, List<Requisition> autoReleasedWithoutOrder) {
Profiler profiler = new Profiler("RELEASE_REQUISITIONS_AS_ORDER");
profiler.setLogger(LOGGER);

Expand All @@ -581,6 +584,13 @@ private List<Requisition> releaseRequisitionsAsOrder(
.orElseThrow(() -> new ContentNotFoundMessageException(ERROR_REQUISITION_NOT_FOUND,
requisitionId));
isEligibleForConvertToOrder(loadedRequisition).throwExceptionIfHasErrors();

if (!loadedRequisition.hasNonZeroPacksToShip()) {
loadedRequisition.releaseWithoutOrder(authenticationHelper.getCurrentUser().getId());
autoReleasedWithoutOrder.add(loadedRequisition);
continue;
}

loadedRequisition.release(authenticationHelper.getCurrentUser().getId());

UUID facilityId = convertToOrderDto.getSupplyingDepotId();
Expand Down Expand Up @@ -713,24 +723,36 @@ public List<Requisition> convertToOrder(List<ReleasableRequisitionDto> list, Use

/**
* Converting Requisition list to Orders.
* Requisitions with all zero packsToShip are automatically released without order.
*/
public List<Requisition> convertToOrder(List<ReleasableRequisitionDto> list, UserDto user,
Boolean isLocallyFulfilled) {
Profiler profiler = new Profiler("CONVERT_TO_ORDER");
profiler.setLogger(LOGGER);

profiler.start("RELEASE_REQUISITIONS_AS_ORDER");
List<Requisition> releasedRequisitions =
releaseRequisitionsAsOrder(list, user, isLocallyFulfilled);
List<Requisition> autoReleasedWithoutOrder = new ArrayList<>();
final List<Requisition> releasedRequisitions =
releaseRequisitionsAsOrder(list, user, isLocallyFulfilled, autoReleasedWithoutOrder);

profiler.start("SAVE_AUTO_RELEASED_WITHOUT_ORDER");
for (Requisition requisition : autoReleasedWithoutOrder) {
requisitionRepository.save(requisition);
requisitionStatusProcessor.statusChange(requisition, LocaleContextHolder.getLocale());
}

profiler.start("BUILD_ORDER_DTOS_AND_SAVE_REQUISITION");
List<OrderDto> orders = buildOrders(releasedRequisitions, user);

profiler.start("CREATE_ORDER_IN_FULFILLMENT");
orderFulfillmentService.create(orders);
if (!orders.isEmpty()) {
orderFulfillmentService.create(orders);
}

profiler.stop().log();
return releasedRequisitions;
List<Requisition> allReleased = new ArrayList<>(releasedRequisitions);
allReleased.addAll(autoReleasedWithoutOrder);
return allReleased;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,70 @@
assertEquals(skipped.getId(), requisitionLineItem.getId());
}

@Test
public void shouldHaveNonZeroPacksToShipWhenAnyNonSkippedLineIsPositive() {
RequisitionLineItem zeroLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(false).withPacksToShip(0L).build();
RequisitionLineItem positiveLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(false).withPacksToShip(5L).build();

Requisition requisition = new RequisitionDataBuilder()

Check warning on line 944 in src/test/java/org/openlmis/requisition/domain/requisition/RequisitionTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "requisition" which hides the field declared at line 125.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUJqvxKY-zkohSHZ&open=AZ65NUJqvxKY-zkohSHZ&pullRequest=128
.addLineItem(zeroLine, false)
.addLineItem(positiveLine, false)
.build();

assertTrue(requisition.hasNonZeroPacksToShip());
}

@Test
public void shouldNotHaveNonZeroPacksToShipWhenAllNonSkippedLinesAreZero() {
RequisitionLineItem firstZeroLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(false).withPacksToShip(0L).build();
RequisitionLineItem secondZeroLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(false).withPacksToShip(0L).build();

Requisition requisition = new RequisitionDataBuilder()

Check warning on line 959 in src/test/java/org/openlmis/requisition/domain/requisition/RequisitionTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "requisition" which hides the field declared at line 125.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUJqvxKY-zkohSHa&open=AZ65NUJqvxKY-zkohSHa&pullRequest=128
.addLineItem(firstZeroLine, false)
.addLineItem(secondZeroLine, false)
.build();

assertFalse(requisition.hasNonZeroPacksToShip());
}

@Test
public void shouldNotHaveNonZeroPacksToShipWhenAllNonSkippedLinesAreNull() {
RequisitionLineItem nullLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(false).withPacksToShip(null).build();

Requisition requisition = new RequisitionDataBuilder()

Check warning on line 972 in src/test/java/org/openlmis/requisition/domain/requisition/RequisitionTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "requisition" which hides the field declared at line 125.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUJqvxKY-zkohSHb&open=AZ65NUJqvxKY-zkohSHb&pullRequest=128
.addLineItem(nullLine, false)
.build();

assertFalse(requisition.hasNonZeroPacksToShip());
}

@Test
public void shouldIgnoreSkippedLinesWhenCheckingPacksToShip() {
RequisitionLineItem zeroNonSkippedLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(false).withPacksToShip(0L).build();
RequisitionLineItem positiveSkippedLine = new RequisitionLineItemDataBuilder()
.withSkippedFlag(true).withPacksToShip(5L).build();

Requisition requisition = new RequisitionDataBuilder()

Check warning on line 986 in src/test/java/org/openlmis/requisition/domain/requisition/RequisitionTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "requisition" which hides the field declared at line 125.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUJqvxKY-zkohSHc&open=AZ65NUJqvxKY-zkohSHc&pullRequest=128
.addLineItem(zeroNonSkippedLine, false)
.addLineItem(positiveSkippedLine, false)
.build();

assertFalse(requisition.hasNonZeroPacksToShip());
}

@Test
public void shouldNotHaveNonZeroPacksToShipWhenThereAreNoLineItems() {
Requisition requisition = new RequisitionDataBuilder().build();

Check warning on line 996 in src/test/java/org/openlmis/requisition/domain/requisition/RequisitionTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "requisition" which hides the field declared at line 125.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUJqvxKY-zkohSHd&open=AZ65NUJqvxKY-zkohSHd&pullRequest=128

assertFalse(requisition.hasNonZeroPacksToShip());
}

@Test
public void shouldUpdatePacksToShipOnSubmit() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1711,7 +1711,10 @@
requisition.setId(UUID.randomUUID());
requisition.setSupervisoryNodeId(UUID.randomUUID());
requisition.setSupplyingFacilityId(facility.getId());
requisition.setRequisitionLineItems(Lists.newArrayList());
RequisitionLineItem nonZeroLine = new RequisitionLineItemDataBuilder()
.withPacksToShip(5L)
.build();
requisition.setRequisitionLineItems(Lists.newArrayList(nonZeroLine));
requisition.setTemplate(requisitionTemplate);

when(requisitionRepository.findById(requisition.getId()))
Expand All @@ -1724,6 +1727,38 @@
return result;
}

private List<ReleasableRequisitionDto> setUpZeroPacksToShipRequisitions(
int amount, RequisitionStatus status) {
if (amount < 1) {
throw new IllegalArgumentException("Amount must be a positive number");
}

List<ReleasableRequisitionDto> result = new ArrayList<>();

for (int i = 0; i < amount; i++) {
FacilityDto facility = mock(FacilityDto.class);

Check warning on line 1739 in src/test/java/org/openlmis/requisition/service/RequisitionServiceTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "facility" which hides the field declared at line 291.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUHCvxKY-zkohSHW&open=AZ65NUHCvxKY-zkohSHW&pullRequest=128
when(facility.getId()).thenReturn(UUID.randomUUID());

Requisition requisition = new Requisition(UUID.randomUUID(), UUID.randomUUID(),

Check warning on line 1742 in src/test/java/org/openlmis/requisition/service/RequisitionServiceTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename "requisition" which hides the field declared at line 187.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUHCvxKY-zkohSHX&open=AZ65NUHCvxKY-zkohSHX&pullRequest=128
UUID.randomUUID(), status, false);
requisition.setId(UUID.randomUUID());
requisition.setSupervisoryNodeId(UUID.randomUUID());
requisition.setSupplyingFacilityId(facility.getId());
RequisitionLineItem zeroLine = new RequisitionLineItemDataBuilder()
.withPacksToShip(0L)
.build();
requisition.setRequisitionLineItems(Lists.newArrayList(zeroLine));
requisition.setTemplate(requisitionTemplate);

when(requisitionRepository.findById(requisition.getId()))
.thenReturn(Optional.of(requisition));

result.add(new ReleasableRequisitionDto(requisition.getId(), facility.getId()));
}

return result;
}

private Requisition generateRequisition() {
lineItem1 = new RequisitionLineItemDataBuilder().build();
lineItem2 = new RequisitionLineItemDataBuilder().build();
Expand Down Expand Up @@ -1997,4 +2032,66 @@
when(supportedProgram.getSupportStartDate()).thenReturn(null);
}

@Test
public void shouldAutoReleaseWithoutOrderWhenAllPacksToShipAreZero() {
// given
List<ReleasableRequisitionDto> list = setUpZeroPacksToShipRequisitions(3, APPROVED);
when(fulfillmentFacilitiesReferenceDataService.getFulfillmentFacilities(
user.getId(), convertToOrderRight.getId())).thenReturn(emptyList());

// when
List<Requisition> result = requisitionService.convertToOrder(list, user);

// then
assertEquals(3, result.size());
result.forEach(req -> assertEquals(RELEASED_WITHOUT_ORDER, req.getStatus()));
verify(orderFulfillmentService, never()).create(any(List.class));
}

@Test
public void shouldProcessStatusChangeForZeroPacksToShipRequisitions() {
// given
List<ReleasableRequisitionDto> list = setUpZeroPacksToShipRequisitions(1, APPROVED);
when(fulfillmentFacilitiesReferenceDataService.getFulfillmentFacilities(
user.getId(), convertToOrderRight.getId())).thenReturn(emptyList());

// when
requisitionService.convertToOrder(list, user);

// then
verify(requisitionStatusProcessor).statusChange(any(Requisition.class), any(Locale.class));
}

@Test
public void shouldCreateOrderOnlyForRequisitionsWithNonZeroPacksToShip() {
// given: 2 non-zero + 2 zero
List<ReleasableRequisitionDto> nonZeroList =
setUpReleaseRequisitionsAsOrder(2, APPROVED);
List<ReleasableRequisitionDto> zeroList =
setUpZeroPacksToShipRequisitions(2, APPROVED);
List<ReleasableRequisitionDto> combined = new ArrayList<>(nonZeroList);
combined.addAll(zeroList);

List<FacilityDto> facilities = nonZeroList.stream()
.map(r -> facilityReferenceDataService.findOne(r.getSupplyingDepotId()))
.collect(toList());

Check warning on line 2077 in src/test/java/org/openlmis/requisition/service/RequisitionServiceTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this usage of 'Stream.collect(Collectors.toList())' with 'Stream.toList()' and ensure that the list is unmodified.

See more on https://sonarcloud.io/project/issues?id=OpenLMIS_openlmis-requisition&issues=AZ65NUHCvxKY-zkohSHY&open=AZ65NUHCvxKY-zkohSHY&pullRequest=128
when(fulfillmentFacilitiesReferenceDataService.getFulfillmentFacilities(
user.getId(), convertToOrderRight.getId())).thenReturn(facilities);
when(requisitionForConvertBuilder.getAvailableSupplyingDepots(any(UUID.class)))
.thenReturn(facilities);

// when
List<Requisition> result = requisitionService.convertToOrder(combined, user);

// then: all 4 returned
assertEquals(4, result.size());
long releasedCount = result.stream()
.filter(r -> RELEASED.equals(r.getStatus())).count();
long releasedWithoutOrderCount = result.stream()
.filter(r -> RELEASED_WITHOUT_ORDER.equals(r.getStatus())).count();
assertEquals(2, releasedCount);
assertEquals(2, releasedWithoutOrderCount);
verify(orderFulfillmentService).create(any(List.class));
}

}
Loading