Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MODINVOICE-482] Using the new transaction batch endpoint #468

Merged
merged 6 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
24 changes: 3 additions & 21 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,7 @@
"voucher-storage.voucher-number.get",
"acquisitions-units-storage.units.collection.get",
"acquisitions-units-storage.memberships.collection.get",
"finance.invoice-transaction-summaries.item.post",
SerhiiNosko marked this conversation as resolved.
Show resolved Hide resolved
"finance.invoice-transaction-summaries.item.put",
"finance.order-transaction-summaries.item.put",
"finance.payments.item.post",
"finance.credits.item.post",
"finance.credits.item.put",
"finance.payments.item.put",
"finance.pending-payments.item.post",
"finance.pending-payments.item.put",
"finance.encumbrances.item.put",
"finance.transactions.batch",
"finance.transactions.collection.get",
"finance.funds.item.get",
"finance.funds.collection.get",
Expand All @@ -97,8 +88,7 @@
"finance.exchange-rate.item.get",
"finance.fiscal-years.item.get",
"finance.fiscal-years.collection.get",
"organizations-storage.organizations.item.get",
"finance.release-encumbrance.item.post"
"organizations-storage.organizations.item.get"
]
},
{
Expand Down Expand Up @@ -622,7 +612,7 @@
},
{
"id": "finance.transactions",
"version": "5.0"
"version": "5.1"
},
{
"id": "batch-group-storage.batch-groups",
Expand Down Expand Up @@ -656,14 +646,6 @@
"id": "finance.budgets",
"version": "2.0"
},
{
"id": "finance.invoice-transaction-summaries",
"version": "2.1"
},
{
"id": "finance.order-transaction-summaries",
"version": "1.2"
},
{
"id": "finance.expense-classes",
"version": "3.0"
Expand Down
23 changes: 4 additions & 19 deletions src/main/java/org/folio/config/ServicesConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import org.folio.services.finance.fiscalyear.FiscalYearService;
import org.folio.services.finance.transaction.BaseTransactionService;
import org.folio.services.finance.transaction.EncumbranceService;
import org.folio.services.finance.transaction.InvoiceTransactionSummaryService;
import org.folio.services.finance.transaction.OrderTransactionSummaryService;
import org.folio.services.finance.transaction.PaymentCreditWorkflowService;
import org.folio.services.finance.transaction.PendingPaymentWorkflowService;
import org.folio.services.invoice.BaseInvoiceService;
Expand Down Expand Up @@ -52,9 +50,8 @@ BaseTransactionService transactionService(RestClient restClient) {
}

@Bean
EncumbranceService encumbranceService(BaseTransactionService transactionService,
OrderTransactionSummaryService orderTransactionSummaryService) {
return new EncumbranceService(transactionService, orderTransactionSummaryService);
EncumbranceService encumbranceService(BaseTransactionService transactionService) {
return new EncumbranceService(transactionService);
}

@Bean
Expand Down Expand Up @@ -99,26 +96,15 @@ FundService fundService(RestClient restClient) {
@Bean
PendingPaymentWorkflowService pendingPaymentService(BaseTransactionService baseTransactionService,
EncumbranceService encumbranceService,
InvoiceTransactionSummaryService invoiceTransactionSummaryService,
FundAvailabilityHolderValidator fundAvailabilityValidator) {
return new PendingPaymentWorkflowService(baseTransactionService, encumbranceService, invoiceTransactionSummaryService, fundAvailabilityValidator);
return new PendingPaymentWorkflowService(baseTransactionService, encumbranceService, fundAvailabilityValidator);
}

@Bean
PaymentCreditWorkflowService paymentCreditService(BaseTransactionService baseTransactionService) {
return new PaymentCreditWorkflowService(baseTransactionService);
}

@Bean
InvoiceTransactionSummaryService invoiceTransactionSummaryService(RestClient restClient) {
return new InvoiceTransactionSummaryService(restClient);
}

@Bean
OrderTransactionSummaryService orderTransactionSummaryService(RestClient restClient) {
return new OrderTransactionSummaryService(restClient);
}

@Bean
FundAvailabilityHolderValidator budgetValidationService() {
return new FundAvailabilityHolderValidator();
Expand Down Expand Up @@ -210,12 +196,11 @@ OrderLineService orderLineService(RestClient restClient) {
@Bean
InvoiceCancelService invoiceCancelService(BaseTransactionService baseTransactionService,
EncumbranceService encumbranceService,
InvoiceTransactionSummaryService invoiceTransactionSummaryService,
VoucherService voucherService,
OrderLineService orderLineService,
OrderService orderService,
InvoiceWorkflowDataHolderBuilder invoiceWorkflowDataHolderBuilder) {
return new InvoiceCancelService(baseTransactionService, encumbranceService, invoiceTransactionSummaryService,
return new InvoiceCancelService(baseTransactionService, encumbranceService,
voucherService, orderLineService, orderService, invoiceWorkflowDataHolderBuilder);
}
@Bean
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/folio/invoices/utils/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public enum ErrorCodes {
USER_HAS_NO_PAY_PERMISSIONS("userHasNoInvoicePayPermission", "User does not have permissions to pay this invoice - operation is restricted"),
USER_HAS_NO_CANCEL_PERMISSIONS("userHasNoInvoiceCancelPermission", "User does not have permissions to cancel this invoice - operation is restricted"),
ACQ_UNITS_NOT_FOUND("acqUnitsNotFound", "Acquisitions units assigned to the record not found"),
PENDING_PAYMENT_ERROR("pendingPaymentError", "Failed to create pending payment"),
PENDING_PAYMENT_ERROR("pendingPaymentError", "Failed to create pending payments: %s"),
INVOICE_PAYMENT_FAILURE("invoicePaymentFailure", "Invoice payment failure"),
CURRENT_FISCAL_YEAR_NOT_FOUND("currentFYearNotFound", "Current fiscal year not found for ledger"),
TRANSACTION_CREATION_FAILURE("transactionCreationFailure", "One or more transactions record(s) failed to be created"),
Expand Down
18 changes: 2 additions & 16 deletions src/main/java/org/folio/invoices/utils/ResourcePathResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,14 @@ private ResourcePathResolver() {
public static final String INVOICE_DOCUMENTS = "invoiceDocuments";
public static final String BATCH_VOUCHER_EXPORT_CONFIGS = "batchVoucherExportConfigs";
public static final String BATCH_VOUCHER_EXPORT_CONFIGS_CREDENTIALS = "batchVoucherExportConfigsCredentials";
public static final String INVOICE_TRANSACTION_SUMMARIES = "invoiceSummary";
public static final String ORDER_TRANSACTION_SUMMARIES = "orderSummary";
public static final String FINANCE_STORAGE_TRANSACTIONS = "finance-storage/transactions";
public static final String BATCH_GROUPS = "batch-groups";
public static final String BATCH_VOUCHER_STORAGE = "batch-voucher/batch-vouchers";
public static final String BATCH_VOUCHER_EXPORTS_STORAGE = "batch-voucher/batch-voucher-exports";
public static final String BUDGETS = "finance.budgets";
public static final String CURRENT_BUDGET = "finance.current-budgets";
public static final String LEDGERS = "finance.ledgers";
public static final String FINANCE_TRANSACTIONS = "finance/transactions";
public static final String FINANCE_RELEASE_ENCUMBRANCE = "finance/release-encumbrance";
public static final String FINANCE_INVOICE_PAYMENTS_SUMMARIES = "finance/invoice-payment-summaries";
public static final String FINANCE_PAYMENTS = "finance/payments";
public static final String FINANCE_CREDITS ="finance/credits";
public static final String FINANCE_PENDING_PAYMENTS ="finance/pending-payments";
public static final String FINANCE_BATCH_TRANSACTIONS = "batchTransactions";
public static final String EXPENSE_CLASSES_URL = "expenseClassUrl";
public static final String BUDGET_EXPENSE_CLASSES = "finance-storage.budget-expense-classes";
public static final String FINANCE_EXCHANGE_RATE = "finance/exchange-rate";
Expand Down Expand Up @@ -70,17 +63,11 @@ private ResourcePathResolver() {
apis.put(INVOICE_DOCUMENTS, "/invoice-storage/invoices/%s/documents");
apis.put(BATCH_VOUCHER_EXPORT_CONFIGS, "/batch-voucher-storage/export-configurations");
apis.put(BATCH_VOUCHER_EXPORT_CONFIGS_CREDENTIALS, "/batch-voucher-storage/export-configurations/%s/credentials");
apis.put(INVOICE_TRANSACTION_SUMMARIES, "/finance/invoice-transaction-summaries");
apis.put(ORDER_TRANSACTION_SUMMARIES, "/finance/order-transaction-summaries");
apis.put(BATCH_GROUPS, "/batch-group-storage/batch-groups");
apis.put(BATCH_VOUCHER_STORAGE, "/batch-voucher-storage/batch-vouchers");
apis.put(BATCH_VOUCHER_EXPORTS_STORAGE, "/batch-voucher-storage/batch-voucher-exports");
apis.put(FINANCE_TRANSACTIONS, "/finance/transactions");
apis.put(FINANCE_RELEASE_ENCUMBRANCE, "/finance/release-encumbrance");
apis.put(FINANCE_STORAGE_TRANSACTIONS, "/finance-storage/transactions");
apis.put(FINANCE_INVOICE_PAYMENTS_SUMMARIES, "/finance/invoice-payment-summaries");
apis.put(FINANCE_PAYMENTS, "/finance/payments");
apis.put(FINANCE_CREDITS, "/finance/credits");
apis.put(FINANCE_BATCH_TRANSACTIONS, "/finance/transactions/batch-all-or-nothing");
apis.put(BUDGETS, "/finance/budgets");
apis.put(CURRENT_BUDGET, "/finance/funds/%s/budget");
apis.put(LEDGERS, "/finance/ledgers");
Expand All @@ -89,7 +76,6 @@ private ResourcePathResolver() {
apis.put(FINANCE_EXCHANGE_RATE, "/finance/exchange-rate");
apis.put(TENANT_CONFIGURATION_ENTRIES, "/configurations/entries");
apis.put(FISCAL_YEARS, "/finance/fiscal-years");
apis.put(FINANCE_PENDING_PAYMENTS, "/finance/pending-payments");

SUB_OBJECT_COLLECTION_APIS = Collections.unmodifiableMap(apis);
SUB_OBJECT_ITEM_APIS = Collections.unmodifiableMap(
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/folio/rest/core/RestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ public Future<Void> postEmptyBody(RequestEntry requestEntry, RequestContext requ
.mapEmpty();
}

public <T> Future<Void> postEmptyResponse(String endpoint, T entity, RequestContext requestContext) {
log.info(REQUEST_MESSAGE_LOG_INFO, HttpMethod.POST, endpoint);
log.debug(REQUEST_MESSAGE_LOG_DEBUG, () -> HttpMethod.POST, () -> JsonObject.mapFrom(entity).encodePrettily());
var caseInsensitiveHeader = convertToCaseInsensitiveMap(requestContext.getHeaders());
return getVertxWebClient(requestContext.getContext())
.postAbs(buildAbsEndpoint(caseInsensitiveHeader, endpoint))
.putHeaders(caseInsensitiveHeader)
.expect(SUCCESS_RESPONSE_PREDICATE)
.sendJson(entity)
.onFailure(log::error)
.mapEmpty();
}

protected MultiMap convertToCaseInsensitiveMap(Map<String, String> okapiHeaders) {
return MultiMap.caseInsensitiveMultiMap()
.addAll(okapiHeaders)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,37 @@
import static java.util.stream.Collectors.toList;
import static org.folio.invoices.utils.HelperUtils.collectResultsOnSuccess;
import static org.folio.invoices.utils.HelperUtils.convertIdsToCqlQuery;
import static org.folio.invoices.utils.ResourcePathResolver.FINANCE_RELEASE_ENCUMBRANCE;
import static org.folio.invoices.utils.ResourcePathResolver.FINANCE_BATCH_TRANSACTIONS;
import static org.folio.invoices.utils.ResourcePathResolver.FINANCE_TRANSACTIONS;
import static org.folio.invoices.utils.ResourcePathResolver.resourcesPath;
import static org.folio.rest.RestConstants.MAX_IDS_FOR_GET_RQ;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import io.vertx.core.json.JsonObject;
import org.apache.commons.collections4.CollectionUtils;
import org.folio.okapi.common.GenericCompositeFuture;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.rest.acq.model.finance.Batch;
import org.folio.rest.acq.model.finance.Encumbrance;
import org.folio.rest.acq.model.finance.Transaction;
import org.folio.rest.acq.model.finance.Transaction.TransactionType;
import org.folio.rest.acq.model.finance.TransactionCollection;
import org.folio.rest.acq.model.finance.TransactionPatch;
import org.folio.rest.core.RestClient;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.core.models.RequestEntry;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertxconcurrent.Semaphore;
import one.util.streamex.StreamEx;

public class BaseTransactionService {
private static final Logger logger = LogManager.getLogger();

private static final String TRANSACTIONS_ENDPOINT = resourcesPath(FINANCE_TRANSACTIONS);

private static final Map<TransactionType, String> TRANSACTION_ENDPOINTS = Map.of(
TransactionType.PAYMENT, "/finance/payments",
TransactionType.CREDIT, "/finance/credits",
TransactionType.PENDING_PAYMENT, "/finance/pending-payments",
TransactionType.ENCUMBRANCE, "/finance/encumbrances"
);

private final RestClient restClient;

public BaseTransactionService(RestClient restClient) {
Expand All @@ -51,7 +46,9 @@ public Future<TransactionCollection> getTransactions(String query, int offset, i
.withQuery(query)
.withOffset(offset)
.withLimit(limit);
return restClient.get(requestEntry, TransactionCollection.class, requestContext);
return restClient.get(requestEntry, TransactionCollection.class, requestContext)
.onSuccess(v -> logger.info("getTransactions completed successfully"))
.onFailure(t -> logger.error("getTransactions failed, query={}", query, t));
}

public Future<List<Transaction>> getTransactions(List<String> transactionIds, RequestContext requestContext) {
Expand All @@ -74,63 +71,57 @@ private Future<TransactionCollection> getTransactionsChunk(List<String> transact
return this.getTransactions(query, 0, transactionIds.size(), requestContext);
}

public Future<Transaction> createTransaction(Transaction transaction, RequestContext requestContext) {
return Optional.ofNullable(getEndpoint(transaction))
.map(RequestEntry::new)
.map(requestEntry -> restClient.post(requestEntry, transaction, Transaction.class, requestContext))
.orElseGet(this::unsupportedOperation);
public Future<Void> batchAllOrNothing(List<Transaction> transactionsToCreate, List<Transaction> transactionsToUpdate,
List<String> idsOfTransactionsToDelete, List<TransactionPatch> transactionPatches, RequestContext requestContext) {
Batch batch = new Batch();
if (transactionsToCreate != null) {
transactionsToCreate.forEach(tr -> {
if (tr.getId() == null) {
SerhiiNosko marked this conversation as resolved.
Show resolved Hide resolved
tr.setId(UUID.randomUUID().toString());
}
});
batch.setTransactionsToCreate(transactionsToCreate);
}
if (transactionsToUpdate != null) {
batch.setTransactionsToUpdate(transactionsToUpdate);
}
if (idsOfTransactionsToDelete != null) {
batch.setIdsOfTransactionsToDelete(idsOfTransactionsToDelete);
}
if (transactionPatches != null) {
batch.setTransactionPatches(transactionPatches);
}
String endpoint = resourcesPath(FINANCE_BATCH_TRANSACTIONS);
return restClient.postEmptyResponse(endpoint, batch, requestContext)
.onSuccess(v -> logger.info("batchAllOrNothing completed successfully"))
.onFailure(t -> logger.error("batchAllOrNothing failed, batch={}", JsonObject.mapFrom(batch), t));
}

public Future<Void> updateTransaction(Transaction transaction, RequestContext requestContext) {
return Optional.ofNullable(getByIdEndpoint(transaction))
.map(RequestEntry::new)
.map(requestEntry -> requestEntry.withId(transaction.getId()))
.map(requestEntry -> restClient.put(requestEntry, transaction, requestContext))
.orElseGet(this::unsupportedOperation);
public Future<Void> batchCreate(List<Transaction> transactions, RequestContext requestContext) {
return batchAllOrNothing(transactions, null, null, null, requestContext);
}

public Future<Void> updateTransactions(List<Transaction> transactions, RequestContext requestContext) {
if (CollectionUtils.isEmpty(transactions)) {
return Future.succeededFuture();
}
return requestContext.getContext()
.<List<Future<Void>>>executeBlocking(promise -> {
List<Future<Void>> futures = new ArrayList<>();
var semaphore = new Semaphore(1, requestContext.getContext().owner());
for (Transaction tr : transactions) {
semaphore.acquire(() -> {
var future = updateTransaction(tr, requestContext)
.onComplete(asyncResult -> semaphore.release());

futures.add(future);
if (futures.size() == transactions.size()) {
promise.complete(futures);
}
});
}
})
.compose(GenericCompositeFuture::join)
.mapEmpty();
public Future<Void> batchUpdate(List<Transaction> transactions, RequestContext requestContext) {
return batchAllOrNothing(null, transactions, null, null, requestContext);
}

public <T> Future<T> unsupportedOperation() {
Promise<T> promise = Promise.promise();
promise.fail(new UnsupportedOperationException());
return promise.future();
public Future<Void> batchRelease(List<Transaction> transactions, RequestContext requestContext) {
// NOTE: we will have to use transactionPatches when it is available
damien-git marked this conversation as resolved.
Show resolved Hide resolved
transactions.forEach(tr -> tr.getEncumbrance().setStatus(Encumbrance.Status.RELEASED));
return batchUpdate(transactions, requestContext);
}


public String getEndpoint(Transaction transaction) {
return TRANSACTION_ENDPOINTS.get(transaction.getTransactionType());
public Future<Void> batchUnrelease(List<Transaction> transactions, RequestContext requestContext) {
// NOTE: we will have to use transactionPatches when it is available
transactions.forEach(tr -> tr.getEncumbrance().setStatus(Encumbrance.Status.UNRELEASED));
return batchUpdate(transactions, requestContext);
}

public String getByIdEndpoint(Transaction transaction) {
return TRANSACTION_ENDPOINTS.get(transaction.getTransactionType()) + "/{id}";
}

public Future<Void> releaseEncumbrance(Transaction transaction, RequestContext requestContext) {
RequestEntry requestEntry = new RequestEntry(resourcesPath(FINANCE_RELEASE_ENCUMBRANCE) + "/{id}").withId(transaction.getId());
return restClient.postEmptyBody(requestEntry, requestContext);
public Future<Void> batchCancel(List<Transaction> transactions, RequestContext requestContext) {
// NOTE: we will have to use transactionPatches when it is available
transactions.forEach(tr -> tr.setInvoiceCancelled(true));
return batchUpdate(transactions, requestContext);
}

}
Loading
Loading