Skip to content

Commit

Permalink
[MODFIN-381] - Implement endpoint to process FY finance bulk update
Browse files Browse the repository at this point in the history
  • Loading branch information
azizbekxm committed Dec 3, 2024
1 parent ad4e333 commit 418ed42
Showing 1 changed file with 71 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package org.folio.services.financedata;

import static java.util.Collections.singletonList;
import static org.folio.rest.util.HelperUtils.combineCqlExpressions;
import static org.folio.rest.util.ResourcePathResolver.FINANCE_DATA_STORAGE;
import static org.folio.rest.util.ResourcePathResolver.resourcesPath;

import io.vertx.core.Future;

import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.folio.okapi.common.GenericCompositeFuture;
import org.folio.rest.core.RestClient;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.core.models.RequestEntry;
import org.folio.rest.jaxrs.model.Batch;
import org.folio.rest.jaxrs.model.Budget;
import org.folio.rest.jaxrs.model.FyFinanceData;
import org.folio.rest.jaxrs.model.FyFinanceDataCollection;
import org.folio.rest.jaxrs.model.Transaction;
Expand Down Expand Up @@ -51,62 +53,104 @@ private Future<FyFinanceDataCollection> getFinanceData(String query, int offset,
}

public Future<Void> putFinanceData(FyFinanceDataCollection financeData, RequestContext requestContext) {
// 1. Validate
validateFinanceData(financeData);

// 2. Apply calculation with allocating new value(allocation transaction should be created):
calculateAllocation();
return processAllocationTransaction(financeData, requestContext)
.compose(v -> updateFinanceData(financeData, requestContext))
.compose(v -> processLogs(financeData, requestContext));
}

// 3. Send request to update finance data
updateFinanceData(financeData, requestContext);
private void validateFinanceData(FyFinanceDataCollection financeData) {
if (financeData.getFyFinanceData() == null || financeData.getFyFinanceData().isEmpty()) {
throw new IllegalArgumentException("Finance data collection is empty");
}

// 4. Invoke Bulk Transactions API to create allocation transactions
processTransaction(financeData, requestContext);
financeData.getFyFinanceData().forEach(this::validateFinanceDataItem);
}

// 5. Invoke storage actions logs endpoint to save request payload + status metadata + recordsCount
processLogs(financeData, requestContext);
return null;
private void validateFinanceDataItem(FyFinanceData item) {
// Validate fiscal year code format (assuming it should be "FY" followed by 4 digits)
if (item.getFiscalYearCode() != null && !item.getFiscalYearCode().matches("FY\\d{4}")) {
throw new IllegalArgumentException("Invalid fiscal year code format. Expected FY followed by 4 digits.");
}

validateNumericFields(item);

// Validate that allocation change matches the difference between current and initial allocation
if (item.getAllocationChange() != null && item.getBudgetCurrentAllocation() != null
&& item.getBudgetInitialAllocation() != null) {
double expectedChange = item.getBudgetCurrentAllocation() - item.getBudgetInitialAllocation();
if (Math.abs(item.getAllocationChange() - expectedChange) > 0.01) { // allowing for small floating-point discrepancies
throw new IllegalArgumentException("Allocation change does not match the difference between current and initial allocation");
}
}
}

private void validateFinanceData(FyFinanceDataCollection financeData) {
private void validateNumericFields(FyFinanceData item) {
if (item.getBudgetInitialAllocation() != null && item.getBudgetInitialAllocation() < 0) {
throw new IllegalArgumentException("Budget initial allocation must be non-negative");
}
if (item.getBudgetCurrentAllocation() != null && item.getBudgetCurrentAllocation() < 0) {
throw new IllegalArgumentException("Budget current allocation must be non-negative");
}
if (item.getBudgetAllowableExpenditure() != null
&& (item.getBudgetAllowableExpenditure() < 0 || item.getBudgetAllowableExpenditure() > 100)) {
throw new IllegalArgumentException("Budget allowable expenditure must be between 0 and 100");
}
if (item.getBudgetAllowableEncumbrance() != null
&& (item.getBudgetAllowableEncumbrance() < 0 || item.getBudgetAllowableEncumbrance() > 100)) {
throw new IllegalArgumentException("Budget allowable encumbrance must be between 0 and 100");
}
}

private Future<Void> processAllocationTransaction(FyFinanceDataCollection financeData, RequestContext requestContext) {
return createAllocationTransaction(financeData, requestContext);
}

public Future<Void> createAllocationTransaction(FyFinanceDataCollection fyFinanceDataCollection, RequestContext requestContext) {
var transactions = fyFinanceDataCollection.getFyFinanceData().stream()
var transactionsFuture = fyFinanceDataCollection.getFyFinanceData().stream()
.map(financeData -> createAllocationTransaction(financeData, requestContext))
.toList();

return fiscalYearService.getFiscalYearById(financeData.getFiscalYearId(), requestContext)
.compose(fiscalYear -> transactionService.createTransaction(transaction.withCurrency(fiscalYear.getCurrency()), requestContext));
return GenericCompositeFuture.join(transactionsFuture)
.map(compositeFuture -> {
List<Transaction> transactions = compositeFuture.list();
return createBatchTransaction(transactions, requestContext);
})
.mapEmpty();
}

public Future<Transaction> createAllocationTransaction(FyFinanceData financeData, RequestContext requestContext) {
Transaction transaction = new Transaction()
var transaction = new Transaction()
.withTransactionType(Transaction.TransactionType.ALLOCATION)
.withId(UUID.randomUUID().toString())
.withAmount(financeData.getAllocationChange())
.withAmount(calculateAllocation(financeData))
.withFiscalYearId(financeData.getFiscalYearId())
.withToFundId(financeData.getFundId())
.withSource(Transaction.Source.USER);

return fiscalYearService.getFiscalYearById(financeData.getFiscalYearId(), requestContext)
.compose(fiscalYear -> transactionService.createTransaction(transaction.withCurrency(fiscalYear.getCurrency()), requestContext));
.map(fiscalYear -> transaction.withCurrency(fiscalYear.getCurrency()));
}

private void calculateAllocation() {

private Double calculateAllocation(FyFinanceData financeData) {
var initialAllocation = BigDecimal.valueOf(financeData.getBudgetInitialAllocation());
var allocationChange = BigDecimal.valueOf(financeData.getAllocationChange());
return initialAllocation.add(allocationChange).doubleValue();
}

private void updateFinanceData(FyFinanceDataCollection financeData, RequestContext requestContext) {
// send request to update finance data
public Future<Void> createBatchTransaction(List<Transaction> transactions, RequestContext requestContext) {
Batch batch = new Batch().withTransactionsToUpdate(transactions);
return transactionService.processBatch(batch, requestContext);
}

private void processTransaction(FyFinanceDataCollection financeData, RequestContext requestContext) {
// invoke Bulk Transactions API to create allocation transactions
private Future<Void> updateFinanceData(FyFinanceDataCollection financeData, RequestContext requestContext) {
return restClient.put(resourcesPath(FINANCE_DATA_STORAGE), financeData, requestContext)
.mapEmpty();
}

private void processLogs(FyFinanceDataCollection financeData, RequestContext requestContext) {
// invoke storage actions logs endpoint to save request payload + status metadata + recordsCount
private Future<Void> processLogs(FyFinanceDataCollection financeData, RequestContext requestContext) {
return Future.succeededFuture();
}
}

0 comments on commit 418ed42

Please sign in to comment.