Skip to content

Commit

Permalink
[MODORDERS-1217] FYRO: remove encumbrance links for pending orders
Browse files Browse the repository at this point in the history
  • Loading branch information
damien-git committed Jan 9, 2025
1 parent 64e9383 commit 629be06
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 35 deletions.
56 changes: 34 additions & 22 deletions src/main/java/org/folio/service/orders/OrderRolloverService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import static org.folio.rest.RestConstants.MAX_IDS_FOR_GET_RQ_15;
import static org.folio.rest.RestVerticle.OKAPI_HEADER_TENANT;
import static org.folio.rest.acq.model.finance.LedgerFiscalYearRolloverError.ErrorType.ORDER_ROLLOVER;
import static org.folio.rest.jaxrs.model.PurchaseOrder.WorkflowStatus.CLOSED;
import static org.folio.rest.jaxrs.model.PurchaseOrder.WorkflowStatus.OPEN;
import static org.folio.rest.jaxrs.model.RolloverStatus.ERROR;
import static org.folio.rest.jaxrs.model.RolloverStatus.SUCCESS;

Expand Down Expand Up @@ -154,8 +152,9 @@ private Future<Void> rolloverOrdersByFundIds(List<String> ledgerFundIds, LedgerF
for (var fundIds : fundIdChunks) {
semaphore.acquire(() -> {
// perform rollover for open orders and then for closed orders
var future = rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, OPEN, requestContext)
.andThen(openOrdersResult -> rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, CLOSED, requestContext)
var future = rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency, true, requestContext)
.andThen(openOrdersResult -> rolloverOrdersByFundIds(fundIds, ledgerFYRollover, systemCurrency,
false, requestContext)
.andThen(closedOrdersResult -> throwExceptionIfOneOfFailed(openOrdersResult, closedOrdersResult)))
.onComplete(asyncResult -> semaphore.release());

Expand All @@ -180,8 +179,8 @@ private void throwExceptionIfOneOfFailed(AsyncResult<Void> openOrdersResult, Asy
}

private Future<Void> rolloverOrdersByFundIds(List<String> chunkFundIds, LedgerFiscalYearRollover ledgerFYRollover,
String systemCurrency, PurchaseOrder.WorkflowStatus workflowStatus, RequestContext requestContext) {
var query = buildOpenOrClosedOrderQueryByFundIdsAndTypes(chunkFundIds, workflowStatus, ledgerFYRollover);
String systemCurrency, boolean openOrders, RequestContext requestContext) {
var query = buildOpenOrClosedOrderQueryByFundIdsAndTypes(chunkFundIds, openOrders, ledgerFYRollover);
var totalRecordsFuture = purchaseOrderLineService.getOrderLineCollection(query, 0, 0, requestContext)
.map(PoLineCollection::getTotalRecords);
var ctx = requestContext.getContext();
Expand All @@ -200,8 +199,8 @@ private Future<Void> rolloverOrdersByFundIds(List<String> chunkFundIds, LedgerFi
// can produce "thread blocked" warnings because of large number of data
semaphore.acquire(() -> {
var future = purchaseOrderLineService.getOrderLines(query, atomicChunkCounter.getAndIncrement() * POLINES_CHUNK_SIZE_200, POLINES_CHUNK_SIZE_200, requestContext)
.compose(poLines -> rolloverOrders(systemCurrency, poLines, ledgerFYRollover, workflowStatus, requestContext))
.compose(modifiedPoLines -> saveOrderLines(modifiedPoLines, ledgerFYRollover, workflowStatus, requestContext))
.compose(poLines -> rolloverOrders(systemCurrency, poLines, ledgerFYRollover, openOrders, requestContext))
.compose(modifiedPoLines -> saveOrderLines(modifiedPoLines, ledgerFYRollover, openOrders, requestContext))
.onComplete(asyncResult -> semaphore.release());
futures.add(future);
if (futures.size() == numberOfChunks) {
Expand All @@ -215,25 +214,24 @@ private Future<Void> rolloverOrdersByFundIds(List<String> chunkFundIds, LedgerFi
}

private Future<Void> saveOrderLines(List<PoLine> orderLines, LedgerFiscalYearRollover ledgerFYRollover,
PurchaseOrder.WorkflowStatus workflowStatus, RequestContext requestContext) {
boolean openOrders, RequestContext requestContext) {
logger.info("saveOrderLines:: Saving POLs after rollover processing, size: {}", orderLines.size());
return purchaseOrderLineService.saveOrderLinesWithoutSearchLocationsUpdate(orderLines, requestContext)
.recover(t -> handlePoLineUpdateFailures(orderLines, ledgerFYRollover, workflowStatus, t, requestContext)
.recover(t -> handlePoLineUpdateFailures(orderLines, ledgerFYRollover, openOrders, t, requestContext)
.map(v -> {
throw new HttpException(400, ErrorCodes.PO_LINE_ROLLOVER_FAILED.toError());
}));
}

private Future<Void> handlePoLineUpdateFailures(List<PoLine> poLines, LedgerFiscalYearRollover ledgerFYRollover,
PurchaseOrder.WorkflowStatus workflowStatus,
Throwable t, RequestContext requestContext) {
boolean openOrders, Throwable t, RequestContext requestContext) {
return GenericCompositeFuture.join(poLines.stream()
.map(poLine -> handlePoLineUpdateFailure(poLine, ledgerFYRollover, workflowStatus, t, requestContext))
.map(poLine -> handlePoLineUpdateFailure(poLine, ledgerFYRollover, openOrders, t, requestContext))
.toList())
.mapEmpty();
}

private Future<Void> handlePoLineUpdateFailure(PoLine poLine, LedgerFiscalYearRollover ledgerFYRollover, PurchaseOrder.WorkflowStatus workflowStatus,
private Future<Void> handlePoLineUpdateFailure(PoLine poLine, LedgerFiscalYearRollover ledgerFYRollover, boolean openOrders,
Throwable t, RequestContext requestContext) {
logger.error("PO line {} update failed while making rollover", poLine.getId(), t);
var failureDto = new FailedLedgerRolloverPoLineDto(
Expand All @@ -244,13 +242,13 @@ private Future<Void> handlePoLineUpdateFailure(PoLine poLine, LedgerFiscalYearRo
JsonObject.mapFrom(poLine).encode(), // requestBody
t.getMessage(), // responseBody
((HttpException) t).getCode(), // statusCode
workflowStatus.value() // purchase order workflow status (Open / Closed)
openOrders ? "Open" : "Closed OR Pending" // purchase order workflow status (saved as text)
);
return failedLedgerRolloverPoLineDao.saveFailedRolloverRecord(failureDto, requestContext.getHeaders().get(OKAPI_HEADER_TENANT));
}

private Future<List<PoLine>> rolloverOrders(String systemCurrency, List<PoLine> poLines, LedgerFiscalYearRollover ledgerFYRollover,
PurchaseOrder.WorkflowStatus workflowStatus, RequestContext requestContext) {
boolean openOrders, RequestContext requestContext) {
if (poLines.isEmpty()) {
return Future.succeededFuture(emptyList());
}
Expand All @@ -259,17 +257,17 @@ private Future<List<PoLine>> rolloverOrders(String systemCurrency, List<PoLine>
.collect(toList());
return getEncumbrancesForRollover(poLineIds, ledgerFYRollover, requestContext)
.compose(encumbrances -> {
if (OPEN.equals(workflowStatus)) {
if (openOrders) {
var holders = buildPoLineEncumbrancesHolders(systemCurrency, poLines, encumbrances, requestContext);
var modifiedPoLines = applyPoLinesRolloverChanges(holders);
return Future.succeededFuture(modifiedPoLines);
} else {
return removeEncumbrancesFromClosedPoLines(poLines, encumbrances, requestContext);
return removeEncumbrancesFromPoLines(poLines, encumbrances, requestContext);
}
});
}

private Future<List<PoLine>> removeEncumbrancesFromClosedPoLines(List<PoLine> poLines, List<Transaction> transactions, RequestContext requestContext) {
private Future<List<PoLine>> removeEncumbrancesFromPoLines(List<PoLine> poLines, List<Transaction> transactions, RequestContext requestContext) {
return Future.succeededFuture()
.compose(v -> {
if (transactions.isEmpty()) {
Expand Down Expand Up @@ -427,13 +425,16 @@ private String buildQuery(List<String> orderIds, String queryTemplate, String de
.collect(Collectors.joining(delimiter));
}

protected String buildOpenOrClosedOrderQueryByFundIdsAndTypes(List<String> fundIds, PurchaseOrder.WorkflowStatus workflowStatus, LedgerFiscalYearRollover ledgerFYRollover) {
protected String buildOpenOrClosedOrderQueryByFundIdsAndTypes(List<String> fundIds, boolean openOrders,
LedgerFiscalYearRollover ledgerFYRollover) {
StringBuilder resultQuery = new StringBuilder();
if (!ledgerFYRollover.getEncumbrancesRollover().isEmpty()) {
resultQuery.append("(").append(buildOrderTypesQuery(ledgerFYRollover)).append(")").append(AND);
}
resultQuery.append("(purchaseOrder.workflowStatus==").append(workflowStatus).append(")").append(AND).append("(").append(buildFundIdQuery(fundIds)).append(")");
if (workflowStatus == CLOSED) {
resultQuery.append("(").append(buildOrderStatusQuery(openOrders)).append(")")
.append(AND)
.append("(").append(buildFundIdQuery(fundIds)).append(")");
if (!openOrders) {
// MODORDERS-904 Avoid rollover re-processing of old already processed closed orders in previous fiscal years
resultQuery.append(AND).append("(").append(PO_LINE_NON_EMPTY_ENCUMBRANCE_QUERY).append(")");
}
Expand Down Expand Up @@ -463,4 +464,15 @@ private PurchaseOrder.OrderType convertToOrderType(EncumbranceRollover.OrderType
}
return PurchaseOrder.OrderType.ONGOING;
}

private String buildOrderStatusQuery(boolean openOrders) {
String statusQuery = "purchaseOrder.workflowStatus";
if (openOrders) {
statusQuery += "==";
} else {
statusQuery += "<>";
}
statusQuery += "Open";
return statusQuery;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.folio.rest.jaxrs.model.LedgerFiscalYearRollover;
import org.folio.rest.jaxrs.model.PoLine;
import org.folio.rest.jaxrs.model.PoLineCollection;
import org.folio.rest.jaxrs.model.PurchaseOrder;
import org.folio.rest.jaxrs.model.RolloverStatus;
import org.folio.service.caches.ConfigurationEntriesCache;
import org.folio.service.exchange.ExchangeRateProviderResolver;
Expand All @@ -54,6 +53,7 @@
import org.folio.service.finance.rollover.LedgerRolloverProgressService;
import org.folio.service.finance.transaction.TransactionService;
import org.folio.utils.CurrencyConversionMockHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -99,12 +99,12 @@ public class OrderRolloverServiceTest {
private ArgumentCaptor<List<PoLine>> argumentCaptor;

private RequestContext requestContext;

private final String systemCurrency = "USD";
private AutoCloseable mockitoMocks;

@BeforeEach
public void initMocks() {
MockitoAnnotations.openMocks(this);
mockitoMocks = MockitoAnnotations.openMocks(this);
Map<String, String> okapiHeadersMock = new HashMap<>();
okapiHeadersMock.put(OKAPI_URL, "http://localhost:" + mockPort);
okapiHeadersMock.put(X_OKAPI_TOKEN.getName(), X_OKAPI_TOKEN.getValue());
Expand All @@ -113,6 +113,11 @@ public void initMocks() {
requestContext = new RequestContext(Vertx.vertx().getOrCreateContext(), okapiHeadersMock);
}

@AfterEach
void afterEach() throws Exception {
mockitoMocks.close();
}

@Test
@DisplayName("Should start preview rollover and check that start rollover was invoked")
void shouldStartPreviewRolloverAndCheckThatStartRolloverWasInvoked(VertxTestContext vertxTestContext) {
Expand Down Expand Up @@ -729,40 +734,40 @@ void shouldConvertTotalAmountUsingCorrectExchangeRateProviderMode(String fromCur
private static Stream<Arguments> testBuildOpenOrClosedOrderQueryByFundIdsAndTypesArgs() {
return Stream.of(
Arguments.of("(purchaseOrder.orderType == One-Time) and (purchaseOrder.workflowStatus==Open) and (fundDistribution =/@fundId \"%s\") sortBy metadata.createdDate",
PurchaseOrder.WorkflowStatus.OPEN,
true,
List.of(new EncumbranceRollover().withOrderType(EncumbranceRollover.OrderType.ONE_TIME).withBasedOn(EncumbranceRollover.BasedOn.INITIAL_AMOUNT).withIncreaseBy(0d))),
Arguments.of("(purchaseOrder.orderType == One-Time) and (purchaseOrder.workflowStatus==Closed) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate",
PurchaseOrder.WorkflowStatus.CLOSED,
Arguments.of("(purchaseOrder.orderType == One-Time) and (purchaseOrder.workflowStatus<>Open) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate",
false,
List.of(new EncumbranceRollover().withOrderType(EncumbranceRollover.OrderType.ONE_TIME).withBasedOn(EncumbranceRollover.BasedOn.INITIAL_AMOUNT).withIncreaseBy(0d)))
);
}

@ParameterizedTest
@MethodSource("testBuildOpenOrClosedOrderQueryByFundIdsAndTypesArgs")
void testBuildOpenOrClosedOrderQueryByFundIdsAndTypes(String expectedQueryTemplate, PurchaseOrder.WorkflowStatus workflowStatus, List<EncumbranceRollover> encumbranceRollovers) {
void testBuildOpenOrClosedOrderQueryByFundIdsAndTypes(String expectedQueryTemplate, boolean openOrders, List<EncumbranceRollover> encumbranceRollovers) {
var fundId = UUID.randomUUID().toString();
var ledgerFiscalYearRollover = new LedgerFiscalYearRollover().withId(UUID.randomUUID().toString()).withEncumbrancesRollover(encumbranceRollovers);
var expectedQuery = String.format(expectedQueryTemplate, fundId);
var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), workflowStatus, ledgerFiscalYearRollover);
var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), openOrders, ledgerFiscalYearRollover);
Assertions.assertEquals(expectedQuery, actualQuery);
}

private static Stream<Arguments> testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettingsArgs() {
return Stream.of(
Arguments.of("(purchaseOrder.workflowStatus==Open) and (fundDistribution =/@fundId \"%s\") sortBy metadata.createdDate",
PurchaseOrder.WorkflowStatus.OPEN),
Arguments.of("(purchaseOrder.workflowStatus==Closed) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate",
PurchaseOrder.WorkflowStatus.CLOSED)
true),
Arguments.of("(purchaseOrder.workflowStatus<>Open) and (fundDistribution =/@fundId \"%s\") and (fundDistribution == \"*\\\"encumbrance\\\": \\\"*\") sortBy metadata.createdDate",
false)
);
}

@ParameterizedTest
@MethodSource("testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettingsArgs")
void testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettings(String expectedQueryTemplate, PurchaseOrder.WorkflowStatus workflowStatus) {
void testBuildOpenOrClosedOrderQueryByFundIdsAndTypesWithoutSettings(String expectedQueryTemplate, boolean openOrders) {
var fundId = UUID.randomUUID().toString();
var ledgerFiscalYearRollover = new LedgerFiscalYearRollover().withId(UUID.randomUUID().toString()).withEncumbrancesRollover(List.of());
var expectedQuery = String.format(expectedQueryTemplate, fundId);
var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), workflowStatus, ledgerFiscalYearRollover);
var actualQuery = orderRolloverService.buildOpenOrClosedOrderQueryByFundIdsAndTypes(List.of(fundId), openOrders, ledgerFiscalYearRollover);
Assertions.assertEquals(expectedQuery, actualQuery);
}
}

0 comments on commit 629be06

Please sign in to comment.