diff --git a/ramls/acq-models b/ramls/acq-models index c6ebedd7..38ee0539 160000 --- a/ramls/acq-models +++ b/ramls/acq-models @@ -1 +1 @@ -Subproject commit c6ebedd7de52700d73516076bbdd45cdabb0185c +Subproject commit 38ee053983a272cc907451994d484c19cbedba49 diff --git a/src/main/java/org/folio/rest/impl/FinanceDataApi.java b/src/main/java/org/folio/rest/impl/FinanceDataApi.java index 405c6aec..987af0d7 100644 --- a/src/main/java/org/folio/rest/impl/FinanceDataApi.java +++ b/src/main/java/org/folio/rest/impl/FinanceDataApi.java @@ -6,8 +6,10 @@ import io.vertx.core.Context; import io.vertx.core.Handler; import io.vertx.core.Vertx; + import java.util.Map; import javax.ws.rs.core.Response; + import org.folio.rest.annotations.Validate; import org.folio.rest.core.models.RequestContext; import org.folio.rest.jaxrs.model.FyFinanceDataCollection; @@ -40,7 +42,7 @@ public void getFinanceFinanceData(String query, String totalRecords, int offset, @Validate public void putFinanceFinanceData(FyFinanceDataCollection entity, Map okapiHeaders, Handler> asyncResultHandler, Context vertxContext) { financeDataService.putFinanceData(entity, new RequestContext(vertxContext, okapiHeaders)) - .onSuccess(v -> asyncResultHandler.handle(succeededFuture(buildNoContentResponse()))) + .onSuccess(financeDataCollection -> asyncResultHandler.handle(succeededFuture(buildOkResponse(financeDataCollection)))) .onFailure(fail -> handleErrorResponse(asyncResultHandler, fail)); } } diff --git a/src/main/java/org/folio/services/financedata/FinanceDataService.java b/src/main/java/org/folio/services/financedata/FinanceDataService.java index 7c60d8c2..427844ef 100644 --- a/src/main/java/org/folio/services/financedata/FinanceDataService.java +++ b/src/main/java/org/folio/services/financedata/FinanceDataService.java @@ -7,6 +7,7 @@ import static org.folio.rest.util.ResourcePathResolver.FINANCE_DATA_STORAGE; import static org.folio.rest.util.ResourcePathResolver.resourcesPath; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -90,17 +91,23 @@ private Future getFinanceData(String query, int offset, * @param requestContext request context * @return future with void result */ - public Future putFinanceData(FyFinanceDataCollection financeDataCollection, RequestContext requestContext) { + public Future putFinanceData(FyFinanceDataCollection financeDataCollection, RequestContext requestContext) { log.debug("Trying to update finance data collection with size: {}", financeDataCollection.getTotalRecords()); if (CollectionUtils.isEmpty(financeDataCollection.getFyFinanceData())) { log.info("putFinanceData:: Finance data collection is empty, nothing to update"); - return succeededFuture(); + return succeededFuture(financeDataCollection); } validateFinanceDataCollection(financeDataCollection, getFiscalYearId(financeDataCollection)); + calculateAfterAllocation(financeDataCollection); + if (financeDataCollection.getUpdateType().equals(FyFinanceDataCollection.UpdateType.PREVIEW)) { + log.info("putFinanceData:: Running dry-run mode finance data collection"); + return succeededFuture(financeDataCollection); + } return processAllocationTransaction(financeDataCollection, requestContext) .compose(v -> updateFinanceData(financeDataCollection, requestContext)) + .map(v -> financeDataCollection) .onSuccess(asyncResult -> processLogs(financeDataCollection, requestContext, COMPLETED)) .onFailure(asyncResult -> processLogs(financeDataCollection, requestContext, ERROR)); } @@ -121,6 +128,15 @@ private void validateFinanceDataCollection(FyFinanceDataCollection financeDataCo } } + private void calculateAfterAllocation(FyFinanceDataCollection financeDataCollection) { + financeDataCollection.getFyFinanceData().forEach(financeData -> { + var allocationChange = BigDecimal.valueOf(financeData.getBudgetAllocationChange()); + var initialAllocation = BigDecimal.valueOf(financeData.getBudgetInitialAllocation()); + var afterAllocation = initialAllocation.add(allocationChange); + financeData.setBudgetAfterAllocation(afterAllocation.doubleValue()); + }); + } + private Future processAllocationTransaction(FyFinanceDataCollection fyFinanceDataCollection, RequestContext requestContext) { return fiscalYearService.getFiscalYearById(getFiscalYearId(fyFinanceDataCollection), requestContext) diff --git a/src/test/java/org/folio/rest/impl/FinanceDataApiTest.java b/src/test/java/org/folio/rest/impl/FinanceDataApiTest.java index 533f90a6..6efa5384 100644 --- a/src/test/java/org/folio/rest/impl/FinanceDataApiTest.java +++ b/src/test/java/org/folio/rest/impl/FinanceDataApiTest.java @@ -5,7 +5,6 @@ import static java.util.Collections.emptyList; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; -import static javax.ws.rs.core.Response.Status.NO_CONTENT; import static javax.ws.rs.core.Response.Status.OK; import static org.folio.rest.util.ErrorCodes.GENERIC_ERROR_CODE; import static org.folio.rest.util.RestTestUtils.verifyGet; @@ -138,25 +137,21 @@ void negative_testGetFinanceFinanceDataFailure() { @Test void positive_testPutFinanceFinanceDataSuccess() throws IOException { - var jsonData = getMockData("mockdata/finance-data/fy_finance_data_collection_put.json"); - var jsonObject = new JsonObject(jsonData); - var financeDataCollection = jsonObject.mapTo(FyFinanceDataCollection.class); - + var financeDataCollection = getFinanceDataCollection(); when(financeDataService.putFinanceData(any(FyFinanceDataCollection.class), any(RequestContext.class))) - .thenReturn(succeededFuture(null)); + .thenReturn(succeededFuture(financeDataCollection)); - verifyPut(FINANCE_DATA_ENDPOINT, financeDataCollection, "", NO_CONTENT.getStatusCode()); + var response = verifyPut(FINANCE_DATA_ENDPOINT, financeDataCollection, APPLICATION_JSON, OK.getStatusCode()) + .as(FyFinanceDataCollection.class); + assertEquals(financeDataCollection, response); verify(financeDataService).putFinanceData(eq(financeDataCollection), any(RequestContext.class)); } @Test void negative_testPutFinanceFinanceDataFailure() throws IOException { - var jsonData = getMockData("mockdata/finance-data/fy_finance_data_collection_put.json"); - var jsonObject = new JsonObject(jsonData); - var financeDataCollection = jsonObject.mapTo(FyFinanceDataCollection.class); - - Future failedFuture = failedFuture(new HttpException(500, INTERNAL_SERVER_ERROR.getReasonPhrase())); + var financeDataCollection = getFinanceDataCollection(); + Future failedFuture = failedFuture(new HttpException(500, INTERNAL_SERVER_ERROR.getReasonPhrase())); when(financeDataService.putFinanceData(any(FyFinanceDataCollection.class), any(RequestContext.class))) .thenReturn(failedFuture); @@ -171,9 +166,7 @@ void negative_testPutFinanceFinanceDataFailure() throws IOException { @Test void negative_testPutFinanceFinanceDataBadRequest() throws IOException { - var jsonData = getMockData("mockdata/finance-data/fy_finance_data_collection_put.json"); - var jsonObject = new JsonObject(jsonData); - var financeDataCollection = jsonObject.mapTo(FyFinanceDataCollection.class); + var financeDataCollection = getFinanceDataCollection(); // Modify one field to make it invalid financeDataCollection.getFyFinanceData().get(0).setFiscalYearId(null); @@ -185,26 +178,52 @@ void negative_testPutFinanceFinanceDataBadRequest() throws IOException { } @Test - void testPutFinanceFinanceDataWithEmptyCollection() { - FyFinanceDataCollection entity = new FyFinanceDataCollection() + void positive_testPutFinanceFinanceDataWithEmptyCollection() { + var entity = new FyFinanceDataCollection() .withFyFinanceData(emptyList()) + .withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT) .withTotalRecords(0); when(financeDataService.putFinanceData(any(FyFinanceDataCollection.class), any(RequestContext.class))) - .thenReturn(succeededFuture(null)); + .thenReturn(succeededFuture(entity)); - verifyPut(FINANCE_DATA_ENDPOINT, entity, "", NO_CONTENT.getStatusCode()); + var response = verifyPut(FINANCE_DATA_ENDPOINT, entity, APPLICATION_JSON, OK.getStatusCode()) + .as(FyFinanceDataCollection.class); + assertEquals(entity, response); verify(financeDataService).putFinanceData(eq(entity), any(RequestContext.class)); } + @Test + void positive_testPutFinanceFinanceDataPreviewMode() throws IOException { + var financeDataCollection = getFinanceDataCollection(); + financeDataCollection.setUpdateType(FyFinanceDataCollection.UpdateType.PREVIEW); + + when(financeDataService.putFinanceData(any(FyFinanceDataCollection.class), any(RequestContext.class))) + .thenReturn(succeededFuture(financeDataCollection)); + + var response = verifyPut(FINANCE_DATA_ENDPOINT, financeDataCollection, APPLICATION_JSON, OK.getStatusCode()) + .as(FyFinanceDataCollection.class); + + assertEquals(financeDataCollection, response); + verify(financeDataService).putFinanceData(eq(financeDataCollection), any(RequestContext.class)); + } + + private FyFinanceDataCollection getFinanceDataCollection() throws IOException { + var jsonData = getMockData("mockdata/finance-data/fy_finance_data_collection_put.json"); + var jsonObject = new JsonObject(jsonData); + return jsonObject.mapTo(FyFinanceDataCollection.class); + } + static class ContextConfiguration { - @Bean public FinanceDataService financeDataService() { + @Bean + public FinanceDataService financeDataService() { return mock(FinanceDataService.class); } - @Bean AcqUnitsService acqUnitsService() { + @Bean + AcqUnitsService acqUnitsService() { return mock(AcqUnitsService.class); } } diff --git a/src/test/java/org/folio/services/financedata/FinanceDataServiceTest.java b/src/test/java/org/folio/services/financedata/FinanceDataServiceTest.java index de150a9e..43ae3d08 100644 --- a/src/test/java/org/folio/services/financedata/FinanceDataServiceTest.java +++ b/src/test/java/org/folio/services/financedata/FinanceDataServiceTest.java @@ -12,6 +12,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -89,7 +90,7 @@ void positive_shouldGetFinanceDataWithAcqUnitsRestriction(VertxTestContext vertx String expectedQuery = "(" + acqUnitIdsQuery + ") and (" + query + ")"; int offset = 0; int limit = 10; - FyFinanceDataCollection fyFinanceDataCollection = new FyFinanceDataCollection(); + var fyFinanceDataCollection = new FyFinanceDataCollection(); when(acqUnitsService.buildAcqUnitsCqlClauseForFinanceData(any())).thenReturn(succeededFuture(acqUnitIdsQuery)); when(restClient.get(anyString(), eq(FyFinanceDataCollection.class), any())).thenReturn(succeededFuture(fyFinanceDataCollection)); @@ -110,7 +111,7 @@ void negative_shouldReturnEmptyCollectionWhenFinanceDataNotFound(VertxTestContex String expectedQuery = "(" + noFdUnitAssignedCql + ") and (" + query + ")"; int offset = 0; int limit = 10; - FyFinanceDataCollection emptyCollection = new FyFinanceDataCollection().withTotalRecords(0); + var emptyCollection = new FyFinanceDataCollection().withTotalRecords(0); when(acqUnitsService.buildAcqUnitsCqlClauseForFinanceData(any())).thenReturn(succeededFuture(noFdUnitAssignedCql)); when(restClient.get(anyString(), eq(FyFinanceDataCollection.class), any())).thenReturn(succeededFuture(emptyCollection)); @@ -127,7 +128,9 @@ void negative_shouldReturnEmptyCollectionWhenFinanceDataNotFound(VertxTestContex @Test void positive_testPutFinanceData_PutFinanceDataSuccessfully(VertxTestContext vertxTestContext) { - var financeDataCollection = new FyFinanceDataCollection().withFyFinanceData(List.of(createValidFyFinanceData())); + var financeDataCollection = new FyFinanceDataCollection() + .withFyFinanceData(List.of(createValidFyFinanceData())) + .withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT); var fiscalYear = new FiscalYear().withCurrency("USD"); when(restClient.put(anyString(), any(), any())).thenReturn(succeededFuture()); @@ -148,7 +151,9 @@ void positive_testPutFinanceData_PutFinanceDataSuccessfully(VertxTestContext ver @Test void negative_testPutFinanceData_LogErrorWhenPutFinanceDataFails(VertxTestContext vertxTestContext) { - var financeDataCollection = new FyFinanceDataCollection().withFyFinanceData(List.of(createValidFyFinanceData())); + var financeDataCollection = new FyFinanceDataCollection() + .withFyFinanceData(List.of(createValidFyFinanceData())) + .withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT); var fiscalYear = new FiscalYear().withCurrency("USD"); when(fiscalYearService.getFiscalYearById(any(), any())).thenReturn(succeededFuture(fiscalYear)); @@ -167,10 +172,11 @@ void negative_testPutFinanceData_LogErrorWhenPutFinanceDataFails(VertxTestContex @Test void negative_testPutFinanceData_FailureInProcessAllocationTransaction(VertxTestContext vertxTestContext) { - FyFinanceDataCollection financeData = new FyFinanceDataCollection(); - FyFinanceData data = createValidFyFinanceData(); - financeData.setFyFinanceData(singletonList(data)); - FiscalYear fiscalYear = new FiscalYear().withCurrency("USD"); + var data = createValidFyFinanceData(); + var financeData = new FyFinanceDataCollection() + .withFyFinanceData(singletonList(data)) + .withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT); + var fiscalYear = new FiscalYear().withCurrency("USD"); when(fiscalYearService.getFiscalYearById(any(), any())).thenReturn(succeededFuture(fiscalYear)); when(transactionApiService.processBatch(any(), any())).thenReturn(failedFuture("Process failed")); @@ -185,7 +191,7 @@ void negative_testPutFinanceData_FailureInProcessAllocationTransaction(VertxTest } @Test - void testCreateAllocationTransactionUsingReflection() throws Exception { + void negative_testCreateAllocationTransactionUsingReflection() throws Exception { var data = createValidFyFinanceData(); var fiscalYear = new FiscalYear().withCurrency("USD"); @@ -201,12 +207,13 @@ void testCreateAllocationTransactionUsingReflection() throws Exception { } @Test - void testPutFinanceData_InvalidAllocationChange() { + void negative_testPutFinanceData_InvalidAllocationChange() { var financeData = createValidFyFinanceData(); financeData.setBudgetInitialAllocation(100.0); financeData.setBudgetAllocationChange(-150.0); var collection = new FyFinanceDataCollection() .withFyFinanceData(Collections.singletonList(financeData)) + .withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT) .withTotalRecords(1); var exception = assertThrows(HttpException.class, @@ -215,11 +222,12 @@ void testPutFinanceData_InvalidAllocationChange() { } @Test - void testPutFinanceData_MissingRequiredField() { + void negative_testPutFinanceData_MissingRequiredField() { var financeData = createValidFyFinanceData(); financeData.setBudgetInitialAllocation(null); var collection = new FyFinanceDataCollection() .withFyFinanceData(Collections.singletonList(financeData)) + .withUpdateType(FyFinanceDataCollection.UpdateType.COMMIT) .withTotalRecords(1); var exception = assertThrows(HttpException.class, @@ -227,6 +235,60 @@ void testPutFinanceData_MissingRequiredField() { assertEquals("Budget initial allocation is required", exception.getErrors().getErrors().get(0).getMessage()); } + @Test + void positive_testPutFinanceData_PreviewMode(VertxTestContext vertxTestContext) { + var financeDataCollection = new FyFinanceDataCollection() + .withFyFinanceData(List.of(createValidFyFinanceData())) + .withUpdateType(FyFinanceDataCollection.UpdateType.PREVIEW); + + var future = financeDataService.putFinanceData(financeDataCollection, requestContextMock); + vertxTestContext.assertComplete(future) + .onComplete(result -> { + assertTrue(result.succeeded()); + assertEquals(financeDataCollection, result.result()); + result.result().getFyFinanceData().forEach(financeData -> + assertEquals( + financeData.getBudgetInitialAllocation() + financeData.getBudgetAllocationChange(), + financeData.getBudgetAfterAllocation())); + verify(restClient, never()).put(anyString(), any(), any()); + verify(transactionApiService, never()).processBatch(any(), any()); + verify(fundUpdateLogService, never()).createFundUpdateLog(any(), any()); + vertxTestContext.completeNow(); + }); + } + + @Test + void positive_testPutFinanceData_PreviewModeWithEmptyData(VertxTestContext vertxTestContext) { + var financeDataCollection = new FyFinanceDataCollection() + .withFyFinanceData(Collections.emptyList()) + .withUpdateType(FyFinanceDataCollection.UpdateType.PREVIEW); + + var future = financeDataService.putFinanceData(financeDataCollection, requestContextMock); + vertxTestContext.assertComplete(future) + .onComplete(result -> { + assertTrue(result.succeeded()); + assertEquals(financeDataCollection, result.result()); + verify(restClient, never()).put(anyString(), any(), any()); + verify(transactionApiService, never()).processBatch(any(), any()); + verify(fundUpdateLogService, never()).createFundUpdateLog(any(), any()); + vertxTestContext.completeNow(); + }); + } + + @Test + void negative_testPutFinanceData_PreviewMode_MissingRequiredField(VertxTestContext vertxTestContext) { + var financeData = createValidFyFinanceData(); + financeData.setBudgetInitialAllocation(null); + var financeDataCollection = new FyFinanceDataCollection() + .withFyFinanceData(Collections.singletonList(financeData)) + .withUpdateType(FyFinanceDataCollection.UpdateType.PREVIEW); + + var exception = assertThrows(HttpException.class, + () -> financeDataService.putFinanceData(financeDataCollection, requestContextMock)); + assertEquals("Budget initial allocation is required", exception.getErrors().getErrors().get(0).getMessage()); + vertxTestContext.completeNow(); + } + private FyFinanceData createValidFyFinanceData() { return new FyFinanceData() .withFundId(UUID.randomUUID().toString()) diff --git a/src/test/resources/mockdata/finance-data/fy_finance_data_collection_put.json b/src/test/resources/mockdata/finance-data/fy_finance_data_collection_put.json index da6a564c..7c43b958 100644 --- a/src/test/resources/mockdata/finance-data/fy_finance_data_collection_put.json +++ b/src/test/resources/mockdata/finance-data/fy_finance_data_collection_put.json @@ -28,8 +28,7 @@ "transactionDescription": "End of year adjustment", "transactionTag": { "tagList": ["Urgent", "Review"] - }, - "updateType": "Commit" + } }, { "fiscalYearId": "123e4567-e89b-12d3-a456-426614174005", @@ -59,9 +58,9 @@ "transactionDescription": "Mid-year adjustment", "transactionTag": { "tagList": ["Urgent", "Review"] - }, - "updateType": "Preview" + } } ], + "updateType": "Commit", "totalRecords": 2 }