Skip to content

Commit

Permalink
[MODINVOICE-509] - Invoice cancellation allowed against closed budget (
Browse files Browse the repository at this point in the history
…#444)

(cherry picked from commit ca70662)
  • Loading branch information
Abdulkhakimov authored and Khamidulla Abdulkhakimov committed Nov 3, 2023
1 parent aacc932 commit 66b7203
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 148 deletions.
14 changes: 14 additions & 0 deletions src/main/java/org/folio/InvoiceWorkflowDataHolderBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ public InvoiceWorkflowDataHolderBuilder(ExchangeRateProviderResolver exchangeRat
this.expenseClassRetrieveService = expenseClassRetrieveService;
}

public Future<List<InvoiceWorkflowDataHolder>> buildCompleteHolders(Invoice invoice,
List<InvoiceLine> invoiceLines,
RequestContext requestContext) {
List<InvoiceWorkflowDataHolder> dataHolders = buildHoldersSkeleton(invoiceLines, invoice);
return withFunds(dataHolders, requestContext)
.compose(holders -> withLedgers(holders, requestContext))
.compose(holders -> withBudgets(holders, requestContext))
.map(this::checkMultipleFiscalYears)
.compose(holders -> withFiscalYear(holders, requestContext))
.compose(holders -> withEncumbrances(holders, requestContext))
.compose(holders -> withExpenseClasses(holders, requestContext))
.compose(holders -> withExchangeRate(holders, requestContext));
}

public List<InvoiceWorkflowDataHolder> buildHoldersSkeleton(List<InvoiceLine> lines, Invoice invoice) {
List<InvoiceWorkflowDataHolder> holders = lines.stream()
.flatMap(invoiceLine -> invoiceLine.getFundDistributions().stream()
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/folio/config/ServicesConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,10 @@ InvoiceCancelService invoiceCancelService(BaseTransactionService baseTransaction
InvoiceTransactionSummaryService invoiceTransactionSummaryService,
VoucherService voucherService,
OrderLineService orderLineService,
OrderService orderService) {
OrderService orderService,
InvoiceWorkflowDataHolderBuilder invoiceWorkflowDataHolderBuilder) {
return new InvoiceCancelService(baseTransactionService, encumbranceService, invoiceTransactionSummaryService,
voucherService, orderLineService, orderService);
voucherService, orderLineService, orderService, invoiceWorkflowDataHolderBuilder);
}
@Bean
BatchVoucherService batchVoucherService(RestClient restClient) {
Expand Down
16 changes: 2 additions & 14 deletions src/main/java/org/folio/rest/impl/InvoiceHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ public Future<FiscalYearCollection> getFiscalYearsByInvoiceId(String invoiceId)


private Future<Void> handleExchangeRateChange(Invoice invoice, List<InvoiceLine> invoiceLines) {
return getInvoiceWorkflowDataHolders(invoice, invoiceLines, requestContext)
return holderBuilder.buildCompleteHolders(invoice, invoiceLines, requestContext)
.compose(holders -> holderBuilder.withExistingTransactions(holders, requestContext))
.compose(holders -> pendingPaymentWorkflowService.handlePendingPaymentsUpdate(holders, requestContext))
.compose(aVoid -> updateVoucher(invoice, invoiceLines));
Expand Down Expand Up @@ -502,7 +502,7 @@ private Future<Void> approveInvoice(Invoice invoice, List<InvoiceLine> lines) {
validateBeforeApproval(organization, invoice, lines);
return null;
})
.compose(v -> getInvoiceWorkflowDataHolders(invoice, lines, requestContext))
.compose(v -> holderBuilder.buildCompleteHolders(invoice, lines, requestContext))
.compose(holders -> encumbranceService.updateInvoiceLinesEncumbranceLinks(holders,
holders.get(0).getFiscalYear().getId(), requestContext)
.compose(linesToUpdate -> invoiceLineService.persistInvoiceLines(linesToUpdate, requestContext))
Expand All @@ -529,18 +529,6 @@ private void validateBeforeApproval(Organization organization, Invoice invoice,
validator.validateBeforeApproval(invoice, lines);
}

private Future<List<InvoiceWorkflowDataHolder>> getInvoiceWorkflowDataHolders(Invoice invoice, List<InvoiceLine> lines, RequestContext requestContext) {
List<InvoiceWorkflowDataHolder> dataHolders = holderBuilder.buildHoldersSkeleton(lines, invoice);
return holderBuilder.withFunds(dataHolders, requestContext)
.compose(holders -> holderBuilder.withLedgers(holders, requestContext))
.compose(holders -> holderBuilder.withBudgets(holders, requestContext))
.map(holderBuilder::checkMultipleFiscalYears)
.compose(holders -> holderBuilder.withFiscalYear(holders, requestContext))
.compose(holders -> holderBuilder.withEncumbrances(holders, requestContext))
.compose(holders -> holderBuilder.withExpenseClasses(holders, requestContext))
.compose(holders -> holderBuilder.withExchangeRate(holders, requestContext));
}

private Future<Voucher> updateVoucherWithSystemCurrency(Voucher voucher, List<InvoiceLine> lines) {
if (!CollectionUtils.isEmpty(lines) && !CollectionUtils.isEmpty(lines.get(0).getFundDistributions())) {
String fundId = lines.get(0).getFundDistributions().get(0).getFundId();
Expand Down
22 changes: 4 additions & 18 deletions src/main/java/org/folio/rest/impl/InvoiceLineHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ public Future<Void> updateInvoiceLine(InvoiceLine invoiceLine, RequestContext re
ilProcessing.setInvoice(invoice);
return null;
})
.compose(invoice -> getInvoiceWorkflowDataHolders(ilProcessing, requestContext))
.compose(invoice -> holderBuilder.buildCompleteHolders(ilProcessing.getInvoice(),
Collections.singletonList(ilProcessing.getInvoiceLine()), requestContext))
.compose(holders -> budgetExpenseClassService.checkExpenseClasses(holders, requestContext))
.map(holders -> updateInvoiceFiscalYear(holders, ilProcessing))
.map(holders -> {
Expand Down Expand Up @@ -353,7 +354,8 @@ public Future<InvoiceLine> createInvoiceLine(InvoiceLine invoiceLine) {
})
.compose(v -> protectionHelper.isOperationRestricted(ilProcessing.getInvoice().getAcqUnitIds(),
ProtectedOperationType.CREATE))
.compose(v -> getInvoiceWorkflowDataHolders(ilProcessing, requestContext)
.compose(invoice -> holderBuilder.buildCompleteHolders(ilProcessing.getInvoice(),
Collections.singletonList(ilProcessing.getInvoiceLine()), requestContext)
.compose(holders -> budgetExpenseClassService.checkExpenseClasses(holders, requestContext))
.compose(holders -> generateNewInvoiceLineNumber(holders, ilProcessing, requestContext))
.map(holders -> updateInvoiceFiscalYear(holders, ilProcessing))
Expand Down Expand Up @@ -619,22 +621,6 @@ private List<String> addPoNumberToList(List<String> numbers, String newNumber) {
return newNumbers;
}

private Future<List<InvoiceWorkflowDataHolder>> getInvoiceWorkflowDataHolders(ILProcessing ilProcessing,
RequestContext requestContext) {
List<InvoiceLine> lines = new ArrayList<>();
lines.add(ilProcessing.getInvoiceLine());

List<InvoiceWorkflowDataHolder> dataHolders = holderBuilder.buildHoldersSkeleton(lines, ilProcessing.getInvoice());
return holderBuilder.withFunds(dataHolders, requestContext)
.compose(holders -> holderBuilder.withLedgers(holders, requestContext))
.compose(holders -> holderBuilder.withBudgets(holders, requestContext))
.map(holderBuilder::checkMultipleFiscalYears)
.compose(holders -> holderBuilder.withFiscalYear(holders, requestContext))
.compose(holders -> holderBuilder.withEncumbrances(holders, requestContext))
.compose(holders -> holderBuilder.withExpenseClasses(holders, requestContext))
.compose(holders -> holderBuilder.withExchangeRate(holders, requestContext));
}

private List<InvoiceWorkflowDataHolder> updateInvoiceFiscalYear(List<InvoiceWorkflowDataHolder> holders,
ILProcessing ilProcessing) {

Expand Down
32 changes: 29 additions & 3 deletions src/main/java/org/folio/services/invoice/InvoiceCancelService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.folio.invoices.utils.ErrorCodes.CANCEL_TRANSACTIONS_ERROR;
import static org.folio.invoices.utils.ErrorCodes.CANNOT_CANCEL_INVOICE;
import static org.folio.invoices.utils.ErrorCodes.ERROR_UNRELEASING_ENCUMBRANCES;
import static org.folio.invoices.utils.HelperUtils.INVOICE_ID;
import static org.folio.invoices.utils.HelperUtils.collectResultsOnSuccess;
import static org.folio.invoices.utils.HelperUtils.convertIdsToCqlQuery;
import static org.folio.rest.RestConstants.MAX_IDS_FOR_GET_RQ;
Expand All @@ -22,7 +23,9 @@
import one.util.streamex.StreamEx;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.InvoiceWorkflowDataHolderBuilder;
import org.folio.invoices.rest.exceptions.HttpException;
import org.folio.models.InvoiceWorkflowDataHolder;
import org.folio.rest.acq.model.finance.InvoiceTransactionSummary;
import org.folio.rest.acq.model.finance.Transaction;
import org.folio.rest.acq.model.finance.Transaction.TransactionType;
Expand Down Expand Up @@ -56,19 +59,22 @@ public class InvoiceCancelService {
private final VoucherService voucherService;
private final OrderLineService orderLineService;
private final OrderService orderService;
private final InvoiceWorkflowDataHolderBuilder holderBuilder;

public InvoiceCancelService(BaseTransactionService baseTransactionService,
EncumbranceService encumbranceService,
InvoiceTransactionSummaryService invoiceTransactionSummaryService,
VoucherService voucherService,
OrderLineService orderLineService,
OrderService orderService) {
OrderService orderService,
InvoiceWorkflowDataHolderBuilder holderBuilder) {
this.baseTransactionService = baseTransactionService;
this.encumbranceService = encumbranceService;
this.invoiceTransactionSummaryService = invoiceTransactionSummaryService;
this.voucherService = voucherService;
this.orderLineService = orderLineService;
this.orderService = orderService;
this.holderBuilder = holderBuilder;
}

/**
Expand All @@ -90,6 +96,7 @@ public Future<Void> cancelInvoice(Invoice invoiceFromStorage, List<InvoiceLine>
validateCancelInvoice(invoiceFromStorage);
return null;
})
.compose(v -> validateBudgetsStatus(invoiceFromStorage, lines, requestContext))
.compose(v -> getTransactions(invoiceId, requestContext))
.compose(transactions -> cancelTransactions(invoiceId, transactions, requestContext))
.map(v -> {
Expand All @@ -104,13 +111,32 @@ private void validateCancelInvoice(Invoice invoiceFromStorage) {
List<Invoice.Status> cancellable = List.of(Invoice.Status.APPROVED, Invoice.Status.PAID);
if (!cancellable.contains(invoiceFromStorage.getStatus())) {
List<Parameter> parameters = Collections.singletonList(
new Parameter().withKey("invoiceId").withValue(invoiceFromStorage.getId()));
new Parameter().withKey(INVOICE_ID).withValue(invoiceFromStorage.getId()));
Error error = CANNOT_CANCEL_INVOICE.toError()
.withParameters(parameters);
throw new HttpException(422, error);
}
}

/**
* Performs validation of budget statuses associated with an invoice.
* Associated budgets should have {@link org.folio.rest.acq.model.finance.Budget.BudgetStatus#ACTIVE} status
* to pass validation successfully.
*
* @param invoice The invoice.This parameter is necessary to extract the associated budgets.
* @param lines The list of invoice lines. This parameter is necessary to extract the associated budgets.
* @param requestContext The request context providing additional information.
* @return A `Future` of type `Void`, representing the result of the validation. If the future succeeds,
* it indicates that the validation has been successfully completed, and active budgets have been extracted
* @throws HttpException If no active budgets are found, an exception is thrown.
*/
private Future<Void> validateBudgetsStatus(Invoice invoice, List<InvoiceLine> lines, RequestContext requestContext) {
List<InvoiceWorkflowDataHolder> dataHolders = holderBuilder.buildHoldersSkeleton(lines, invoice);
return holderBuilder.withBudgets(dataHolders, requestContext)
.onFailure(t -> logger.error("Could not find an active budget for the invoice with id {}", invoice.getId(), t))
.mapEmpty();
}

private Future<List<Transaction>> getTransactions(String invoiceId, RequestContext requestContext) {
String query = String.format("sourceInvoiceId==%s", invoiceId);
List<TransactionType> relevantTransactionTypes = List.of(PENDING_PAYMENT, PAYMENT, CREDIT);
Expand All @@ -131,7 +157,7 @@ private Future<Void> cancelTransactions(String invoiceId, List<Transaction> tran
.recover(t -> {
logger.error("Failed to cancel transactions for invoice with id {}", invoiceId, t);
List<Parameter> parameters = Collections.singletonList(
new Parameter().withKey("invoiceId").withValue(invoiceId));
new Parameter().withKey(INVOICE_ID).withValue(invoiceId));
throw new HttpException(500, CANCEL_TRANSACTIONS_ERROR.toError().withParameters(parameters));
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.apache.commons.lang3.StringUtils;
import org.folio.InvoiceWorkflowDataHolderBuilder;
import org.folio.invoices.rest.exceptions.HttpException;
import org.folio.models.InvoiceWorkflowDataHolder;
import org.folio.rest.acq.model.orders.CompositePoLine;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.model.FundDistribution;
Expand Down Expand Up @@ -55,7 +54,7 @@ public class InvoicePaymentService {
public Future<Void> payInvoice(Invoice invoice, List<InvoiceLine> invoiceLines, RequestContext requestContext) {
// Set payment date, when the invoice is being paid.
invoice.setPaymentDate(invoice.getMetadata().getUpdatedDate());
return getInvoiceWorkflowDataHolders(invoice, invoiceLines, requestContext)
return holderBuilder.buildCompleteHolders(invoice, invoiceLines, requestContext)
.compose(holders -> paymentCreditWorkflowService.handlePaymentsAndCreditsCreation(holders, requestContext))
.compose(vVoid -> CompositeFuture.join(updatePoLinesStatus(invoice, invoiceLines, requestContext), voucherService.payInvoiceVoucher(invoice.getId(), requestContext)))
.mapEmpty();
Expand Down Expand Up @@ -85,18 +84,6 @@ private Future<Void> updatePoLinesStatus(Invoice invoice, List<InvoiceLine> invo
return Future.failedFuture(new HttpException(400, INVOICE_LINE_MUST_HAVE_FUND));
}

private Future<List<InvoiceWorkflowDataHolder>> getInvoiceWorkflowDataHolders(Invoice invoice, List<InvoiceLine> lines, RequestContext requestContext) {
List<InvoiceWorkflowDataHolder> dataHolders = holderBuilder.buildHoldersSkeleton(lines, invoice);
return holderBuilder.withFunds(dataHolders, requestContext)
.compose(holders -> holderBuilder.withLedgers(holders, requestContext))
.compose(holders -> holderBuilder.withBudgets(holders, requestContext))
.map(holderBuilder::checkMultipleFiscalYears)
.compose(holders -> holderBuilder.withFiscalYear(holders, requestContext))
.compose(holders -> holderBuilder.withEncumbrances(holders, requestContext))
.compose(holders -> holderBuilder.withExpenseClasses(holders, requestContext))
.compose(holders -> holderBuilder.withExchangeRate(holders, requestContext));
}

/**
* Updates payment status of the associated PO Lines.
*
Expand Down
4 changes: 3 additions & 1 deletion src/test/java/org/folio/TestMockDataConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ public final class TestMockDataConstants {
private TestMockDataConstants() {
}
public static final String BASE_MOCK_DATA_PATH = "mockdata/";
public static final String BASE_MOCK_BUDGETS_BASE_PATH = BASE_MOCK_DATA_PATH + "budgets/";
public static final String BASE_MOCK_TRANSACTIONS_BASE_PATH = BASE_MOCK_DATA_PATH + "transactions/";
public static final String MOCK_TRANSACTIONS_LIST = BASE_MOCK_TRANSACTIONS_BASE_PATH + "transactions.json";
public static final String MOCK_CREDITS_LIST = BASE_MOCK_TRANSACTIONS_BASE_PATH + "credits.json";
public static final String MOCK_BUDGET_ITEM = BASE_MOCK_BUDGETS_BASE_PATH + "budget.json";
public static final String MOCK_BUDGETS_LIST = BASE_MOCK_BUDGETS_BASE_PATH + "budgets.json";
public static final String MOCK_ENCUMBRANCES_LIST = BASE_MOCK_TRANSACTIONS_BASE_PATH + "encumbrances.json";
public static final String MOCK_PAYMENTS_LIST = BASE_MOCK_TRANSACTIONS_BASE_PATH + "payments.json";
public static final String MOCK_PENDING_PAYMENTS_LIST = BASE_MOCK_TRANSACTIONS_BASE_PATH + "pending-payments.json";
Expand Down
Loading

0 comments on commit 66b7203

Please sign in to comment.