Search in sources :

Example 11 with InvoiceWorkflowDataHolder

use of org.folio.models.InvoiceWorkflowDataHolder in project mod-invoice by folio-org.

the class BudgetExpenseClassTest method shouldThrowExceptionWithInactiveExpenseClassCodeWhenCheckExpenseClassesWithInactiveExpenseClass.

@Test
void shouldThrowExceptionWithInactiveExpenseClassCodeWhenCheckExpenseClassesWithInactiveExpenseClass() {
    String inactiveExpenseClassId = UUID.randomUUID().toString();
    String activeExpenseClassId = UUID.randomUUID().toString();
    FundDistribution fundDistributionWithInactiveExpenseClass = new FundDistribution().withFundId(UUID.randomUUID().toString()).withExpenseClassId(inactiveExpenseClassId);
    FundDistribution fundDistributionWithActiveExpenseClass = new FundDistribution().withFundId(UUID.randomUUID().toString()).withExpenseClassId(activeExpenseClassId);
    FundDistribution fundDistributionWithoutExpenseClass = new FundDistribution().withFundId(UUID.randomUUID().toString());
    InvoiceLine invoiceLine = new InvoiceLine().withFundDistributions(Arrays.asList(fundDistributionWithInactiveExpenseClass, fundDistributionWithoutExpenseClass));
    List<InvoiceLine> invoiceLines = Collections.singletonList(invoiceLine);
    Adjustment adjustment = new Adjustment().withFundDistributions(Collections.singletonList(fundDistributionWithActiveExpenseClass));
    Invoice invoice = new Invoice().withAdjustments(Collections.singletonList(adjustment));
    BudgetExpenseClass active = new BudgetExpenseClass().withBudgetId(UUID.randomUUID().toString()).withExpenseClassId(activeExpenseClassId).withStatus(BudgetExpenseClass.Status.ACTIVE).withId(UUID.randomUUID().toString());
    BudgetExpenseClass inactive = new BudgetExpenseClass().withBudgetId(UUID.randomUUID().toString()).withExpenseClassId(inactiveExpenseClassId).withStatus(BudgetExpenseClass.Status.INACTIVE).withId(UUID.randomUUID().toString());
    Fund inactiveExpenseClassFund = new Fund().withCode("inactive fund");
    ExpenseClass inactiveExpenseClass = new ExpenseClass().withName("inactive class");
    List<InvoiceWorkflowDataHolder> holders = new ArrayList<>();
    InvoiceWorkflowDataHolder holder1 = new InvoiceWorkflowDataHolder().withInvoice(invoice).withInvoiceLine(invoiceLine).withFundDistribution(fundDistributionWithInactiveExpenseClass).withExpenseClass(inactiveExpenseClass).withBudget(new Budget().withFundId(UUID.randomUUID().toString())).withFund(inactiveExpenseClassFund);
    InvoiceWorkflowDataHolder holder2 = new InvoiceWorkflowDataHolder().withInvoice(invoice).withInvoiceLine(invoiceLine).withBudget(new Budget().withFundId(UUID.randomUUID().toString())).withFundDistribution(fundDistributionWithoutExpenseClass);
    InvoiceWorkflowDataHolder holder3 = new InvoiceWorkflowDataHolder().withInvoice(invoice).withAdjustment(adjustment).withFundDistribution(fundDistributionWithActiveExpenseClass).withBudget(new Budget().withFundId(UUID.randomUUID().toString())).withExpenseClass(new ExpenseClass().withId(activeExpenseClassId));
    holders.add(holder1);
    holders.add(holder2);
    holders.add(holder3);
    when(restClient.get(any(RequestEntry.class), any(), any())).thenAnswer(invocation -> {
        RequestEntry requestEntry = invocation.getArgument(0);
        BudgetExpenseClass bec = requestEntry.buildEndpoint().contains(activeExpenseClassId) ? active : inactive;
        return CompletableFuture.completedFuture(new BudgetExpenseClassCollection().withBudgetExpenseClasses(Collections.singletonList(bec)).withTotalRecords(1));
    });
    when(requestContext.getContext()).thenReturn(Vertx.vertx().getOrCreateContext());
    CompletableFuture<List<InvoiceWorkflowDataHolder>> future = budgetExpenseClassService.checkExpenseClasses(holders, requestContext);
    ExecutionException executionException = assertThrows(ExecutionException.class, future::get);
    assertThat(executionException.getCause(), instanceOf(HttpException.class));
    HttpException exception = (HttpException) executionException.getCause();
    assertEquals(400, exception.getCode());
    Errors errors = exception.getErrors();
    Error error = errors.getErrors().get(0);
    assertEquals(INACTIVE_EXPENSE_CLASS.getCode(), error.getCode());
    String expenseClassNameFromError = error.getParameters().stream().filter(parameter -> parameter.getKey().equals(EXPENSE_CLASS_NAME)).findFirst().get().getValue();
    assertEquals(inactiveExpenseClass.getName(), expenseClassNameFromError);
    String fundCodeFromError = error.getParameters().stream().filter(parameter -> parameter.getKey().equals(FUND_CODE)).findFirst().get().getValue();
    assertEquals(inactiveExpenseClassFund.getCode(), fundCodeFromError);
}
Also used : ExpenseClass(org.folio.rest.acq.model.finance.ExpenseClass) BudgetExpenseClass(org.folio.rest.acq.model.finance.BudgetExpenseClass) Assertions.assertThrows(org.junit.jupiter.api.Assertions.assertThrows) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) RestClient(org.folio.rest.core.RestClient) BeforeEach(org.junit.jupiter.api.BeforeEach) Arrays(java.util.Arrays) Mock(org.mockito.Mock) CompletableFuture(java.util.concurrent.CompletableFuture) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) ArrayList(java.util.ArrayList) HttpException(org.folio.invoices.rest.exceptions.HttpException) MockitoAnnotations(org.mockito.MockitoAnnotations) ExpenseClass(org.folio.rest.acq.model.finance.ExpenseClass) FUND_CODE(org.folio.services.finance.budget.BudgetExpenseClassService.FUND_CODE) BudgetExpenseClass(org.folio.rest.acq.model.finance.BudgetExpenseClass) RequestContext(org.folio.rest.core.models.RequestContext) MatcherAssert.assertThat(org.hamcrest.MatcherAssert.assertThat) Assertions.assertEquals(org.junit.jupiter.api.Assertions.assertEquals) Errors(org.folio.rest.jaxrs.model.Errors) Budget(org.folio.rest.acq.model.finance.Budget) InvoiceLine(org.folio.rest.jaxrs.model.InvoiceLine) INACTIVE_EXPENSE_CLASS(org.folio.invoices.utils.ErrorCodes.INACTIVE_EXPENSE_CLASS) BudgetExpenseClassService(org.folio.services.finance.budget.BudgetExpenseClassService) Vertx(io.vertx.core.Vertx) RequestEntry(org.folio.rest.core.models.RequestEntry) Mockito.when(org.mockito.Mockito.when) UUID(java.util.UUID) Fund(org.folio.rest.acq.model.finance.Fund) FundDistribution(org.folio.rest.jaxrs.model.FundDistribution) Matchers.instanceOf(org.hamcrest.Matchers.instanceOf) ExecutionException(java.util.concurrent.ExecutionException) Test(org.junit.jupiter.api.Test) Error(org.folio.rest.jaxrs.model.Error) List(java.util.List) BudgetExpenseClassCollection(org.folio.rest.acq.model.finance.BudgetExpenseClassCollection) EXPENSE_CLASS_NAME(org.folio.services.finance.budget.BudgetExpenseClassService.EXPENSE_CLASS_NAME) Adjustment(org.folio.rest.jaxrs.model.Adjustment) BUDGET_EXPENSE_CLASS_NOT_FOUND(org.folio.invoices.utils.ErrorCodes.BUDGET_EXPENSE_CLASS_NOT_FOUND) Collections(java.util.Collections) Invoice(org.folio.rest.jaxrs.model.Invoice) Invoice(org.folio.rest.jaxrs.model.Invoice) Adjustment(org.folio.rest.jaxrs.model.Adjustment) BudgetExpenseClass(org.folio.rest.acq.model.finance.BudgetExpenseClass) Fund(org.folio.rest.acq.model.finance.Fund) InvoiceLine(org.folio.rest.jaxrs.model.InvoiceLine) ArrayList(java.util.ArrayList) Error(org.folio.rest.jaxrs.model.Error) RequestEntry(org.folio.rest.core.models.RequestEntry) FundDistribution(org.folio.rest.jaxrs.model.FundDistribution) Errors(org.folio.rest.jaxrs.model.Errors) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) Budget(org.folio.rest.acq.model.finance.Budget) ArrayList(java.util.ArrayList) List(java.util.List) HttpException(org.folio.invoices.rest.exceptions.HttpException) ExecutionException(java.util.concurrent.ExecutionException) BudgetExpenseClassCollection(org.folio.rest.acq.model.finance.BudgetExpenseClassCollection) Test(org.junit.jupiter.api.Test)

Example 12 with InvoiceWorkflowDataHolder

use of org.folio.models.InvoiceWorkflowDataHolder in project mod-invoice by folio-org.

the class FundAvailabilityHolderValidator method calculateTotalExpendedAmount.

private MonetaryAmount calculateTotalExpendedAmount(List<InvoiceWorkflowDataHolder> dataHolders) {
    CurrencyUnit currency = Monetary.getCurrency(dataHolders.get(0).getFyCurrency());
    return dataHolders.stream().map(holder -> {
        MonetaryAmount newTransactionAmount = Money.of(holder.getNewTransaction().getAmount(), holder.getFyCurrency());
        MonetaryAmount existingTransactionAmount = Optional.ofNullable(holder.getExistingTransaction()).map(transaction -> Money.of(transaction.getAmount(), transaction.getCurrency())).orElseGet(() -> Money.zero(Monetary.getCurrency(holder.getFyCurrency())));
        return newTransactionAmount.subtract(existingTransactionAmount);
    }).reduce(MonetaryFunctions::sum).orElseGet(() -> Money.zero(currency));
}
Also used : Monetary(javax.money.Monetary) FUND_CANNOT_BE_PAID(org.folio.invoices.utils.ErrorCodes.FUND_CANNOT_BE_PAID) CurrencyUnit(javax.money.CurrencyUnit) Money(org.javamoney.moneta.Money) Collectors.groupingBy(java.util.stream.Collectors.groupingBy) MonetaryFunctions(org.javamoney.moneta.function.MonetaryFunctions) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) Fund(org.folio.rest.acq.model.finance.Fund) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) BigDecimal(java.math.BigDecimal) HttpException(org.folio.invoices.rest.exceptions.HttpException) Collectors.toList(java.util.stream.Collectors.toList) List(java.util.List) FUNDS(org.folio.invoices.utils.ResourcePathResolver.FUNDS) Map(java.util.Map) Optional(java.util.Optional) Parameter(org.folio.rest.jaxrs.model.Parameter) Collections(java.util.Collections) MonetaryAmount(javax.money.MonetaryAmount) Budget(org.folio.rest.acq.model.finance.Budget) CurrencyUnit(javax.money.CurrencyUnit) MonetaryAmount(javax.money.MonetaryAmount)

Example 13 with InvoiceWorkflowDataHolder

use of org.folio.models.InvoiceWorkflowDataHolder in project mod-invoice by folio-org.

the class FundAvailabilityHolderValidator method validate.

@Override
public void validate(List<InvoiceWorkflowDataHolder> dataHolders) {
    Map<Budget, List<InvoiceWorkflowDataHolder>> budgetHoldersMap = dataHolders.stream().filter(InvoiceWorkflowDataHolder::isRestrictExpenditures).collect(groupingBy(InvoiceWorkflowDataHolder::getBudget));
    Map<String, String> fundHoldersMap = dataHolders.stream().filter(InvoiceWorkflowDataHolder::isRestrictExpenditures).map(InvoiceWorkflowDataHolder::getFund).filter(Objects::nonNull).collect(Collectors.toMap(Fund::getId, Fund::getCode, (fundEntityKey, fundEntityDupKey) -> fundEntityKey));
    List<String> failedBudgetIds = budgetHoldersMap.entrySet().stream().filter(entry -> Objects.nonNull(entry.getKey().getAllowableExpenditure())).filter(entry -> {
        MonetaryAmount newExpendedAmount = calculateNewExpendedAmount(entry.getValue());
        MonetaryAmount totalExpendedAmount = calculateTotalExpendedAmount(entry.getValue());
        return isRemainingAmountExceed(entry.getKey(), newExpendedAmount, totalExpendedAmount);
    }).map(Map.Entry::getKey).map(Budget::getFundId).collect(toList());
    if (!failedBudgetIds.isEmpty()) {
        Parameter parameter = new Parameter().withKey(FUNDS).withValue(failedBudgetIds.stream().map(fundHoldersMap::get).collect(toList()).toString());
        throw new HttpException(422, FUND_CANNOT_BE_PAID.toError().withParameters(Collections.singletonList(parameter)));
    }
}
Also used : Monetary(javax.money.Monetary) FUND_CANNOT_BE_PAID(org.folio.invoices.utils.ErrorCodes.FUND_CANNOT_BE_PAID) CurrencyUnit(javax.money.CurrencyUnit) Money(org.javamoney.moneta.Money) Collectors.groupingBy(java.util.stream.Collectors.groupingBy) MonetaryFunctions(org.javamoney.moneta.function.MonetaryFunctions) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) Fund(org.folio.rest.acq.model.finance.Fund) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) BigDecimal(java.math.BigDecimal) HttpException(org.folio.invoices.rest.exceptions.HttpException) Collectors.toList(java.util.stream.Collectors.toList) List(java.util.List) FUNDS(org.folio.invoices.utils.ResourcePathResolver.FUNDS) Map(java.util.Map) Optional(java.util.Optional) Parameter(org.folio.rest.jaxrs.model.Parameter) Collections(java.util.Collections) MonetaryAmount(javax.money.MonetaryAmount) Budget(org.folio.rest.acq.model.finance.Budget) MonetaryAmount(javax.money.MonetaryAmount) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) Parameter(org.folio.rest.jaxrs.model.Parameter) Budget(org.folio.rest.acq.model.finance.Budget) Collectors.toList(java.util.stream.Collectors.toList) List(java.util.List) HttpException(org.folio.invoices.rest.exceptions.HttpException)

Example 14 with InvoiceWorkflowDataHolder

use of org.folio.models.InvoiceWorkflowDataHolder in project mod-invoice by folio-org.

the class FundAvailabilityHolderValidatorTest method checkEnoughMoneyInBudgetShouldPassIfTransactionsAmountDifferenceLessThanBudgetRemainingAmount.

@Test
void checkEnoughMoneyInBudgetShouldPassIfTransactionsAmountDifferenceLessThanBudgetRemainingAmount() {
    String fiscalYearId = UUID.randomUUID().toString();
    String fundId = UUID.randomUUID().toString();
    String ledgerId = UUID.randomUUID().toString();
    String budgetId = UUID.randomUUID().toString();
    Transaction existingTransaction = new Transaction().withTransactionType(Transaction.TransactionType.PENDING_PAYMENT).withAmount(50d).withFiscalYearId(fiscalYearId).withFromFundId(fundId).withCurrency("USD");
    Transaction newTransaction = new Transaction().withTransactionType(Transaction.TransactionType.PENDING_PAYMENT).withAmount(60d).withFiscalYearId(fiscalYearId).withFromFundId(fundId).withCurrency("USD");
    Budget budget = new Budget().withId(budgetId).withFiscalYearId(fiscalYearId).withFundId(fundId).withAllocated(59d).withAvailable(9d).withTotalFunding(59d).withUnavailable(50d).withAwaitingPayment(50D).withAllowableExpenditure(150d);
    Fund fund = new Fund().withId(fundId).withLedgerId(ledgerId).withCode("FC").withFundStatus(Fund.FundStatus.ACTIVE);
    Ledger ledger = new Ledger().withId(ledgerId).withRestrictExpenditures(true);
    List<InvoiceWorkflowDataHolder> holders = new ArrayList<>();
    InvoiceWorkflowDataHolder holder = new InvoiceWorkflowDataHolder().withExistingTransaction(existingTransaction).withNewTransaction(newTransaction).withBudget(budget).withFund(fund).withRestrictExpenditures(ledger.getRestrictExpenditures()).withFiscalYear(new FiscalYear().withId(fiscalYearId).withCurrency("USD"));
    holders.add(holder);
    assertDoesNotThrow(() -> fundAvailabilityValidator.validate(holders));
}
Also used : FiscalYear(org.folio.rest.acq.model.finance.FiscalYear) Transaction(org.folio.rest.acq.model.finance.Transaction) Fund(org.folio.rest.acq.model.finance.Fund) Ledger(org.folio.rest.acq.model.finance.Ledger) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) ArrayList(java.util.ArrayList) Budget(org.folio.rest.acq.model.finance.Budget) Test(org.junit.jupiter.api.Test)

Example 15 with InvoiceWorkflowDataHolder

use of org.folio.models.InvoiceWorkflowDataHolder in project mod-invoice by folio-org.

the class FundAvailabilityHolderValidatorTest method shouldPassValidationWhenBudgetRestrictedAndAmountGreaterThanAvailableAndRequiredAmountEncumbered.

@Test
void shouldPassValidationWhenBudgetRestrictedAndAmountGreaterThanAvailableAndRequiredAmountEncumbered() {
    String fiscalYearId = UUID.randomUUID().toString();
    String fundId = UUID.randomUUID().toString();
    String budgetId = UUID.randomUUID().toString();
    String ledgerId = UUID.randomUUID().toString();
    FiscalYear fiscalYear = new FiscalYear().withCurrency("USD").withId(fiscalYearId);
    Fund fund = new Fund().withId(fundId).withName("TestFund").withLedgerId(ledgerId).withCode("FC").withFundStatus(Fund.FundStatus.ACTIVE);
    Budget budget = new Budget().withId(budgetId).withFiscalYearId(fiscalYearId).withFundId(fundId).withAllocated(260d).withTotalFunding(260d).withAvailable(20d).withUnavailable(240d).withEncumbered(200d).withAwaitingPayment(40d).withAllowableExpenditure(100d);
    List<InvoiceWorkflowDataHolder> holders = new ArrayList<>();
    Transaction adjustmentPendingPayment = new Transaction().withAmount(20d).withCurrency("USD");
    Transaction encumbrance = new Transaction().withId(UUID.randomUUID().toString()).withAmount(200d).withCurrency("USD");
    Transaction linePendingPayment = new Transaction().withAmount(200d).withAwaitingPayment(new AwaitingPayment().withEncumbranceId(encumbrance.getId()).withReleaseEncumbrance(false)).withCurrency("USD");
    InvoiceWorkflowDataHolder holder1 = new InvoiceWorkflowDataHolder().withFund(fund).withBudget(budget).withRestrictExpenditures(true).withFiscalYear(fiscalYear).withNewTransaction(linePendingPayment).withEncumbrance(encumbrance);
    InvoiceWorkflowDataHolder holder2 = new InvoiceWorkflowDataHolder().withFund(fund).withBudget(budget).withRestrictExpenditures(true).withFiscalYear(fiscalYear).withNewTransaction(adjustmentPendingPayment);
    holders.add(holder1);
    holders.add(holder2);
    assertDoesNotThrow(() -> fundAvailabilityValidator.validate(holders));
}
Also used : FiscalYear(org.folio.rest.acq.model.finance.FiscalYear) Fund(org.folio.rest.acq.model.finance.Fund) Transaction(org.folio.rest.acq.model.finance.Transaction) InvoiceWorkflowDataHolder(org.folio.models.InvoiceWorkflowDataHolder) ArrayList(java.util.ArrayList) Budget(org.folio.rest.acq.model.finance.Budget) AwaitingPayment(org.folio.rest.acq.model.finance.AwaitingPayment) Test(org.junit.jupiter.api.Test)

Aggregations

InvoiceWorkflowDataHolder (org.folio.models.InvoiceWorkflowDataHolder)17 ArrayList (java.util.ArrayList)12 HttpException (org.folio.invoices.rest.exceptions.HttpException)11 Budget (org.folio.rest.acq.model.finance.Budget)11 Fund (org.folio.rest.acq.model.finance.Fund)11 Transaction (org.folio.rest.acq.model.finance.Transaction)11 List (java.util.List)10 Test (org.junit.jupiter.api.Test)9 MonetaryAmount (javax.money.MonetaryAmount)7 FiscalYear (org.folio.rest.acq.model.finance.FiscalYear)7 Optional (java.util.Optional)6 Collectors.toList (java.util.stream.Collectors.toList)6 RequestContext (org.folio.rest.core.models.RequestContext)6 InvoiceLine (org.folio.rest.jaxrs.model.InvoiceLine)6 Collections (java.util.Collections)5 CompletableFuture (java.util.concurrent.CompletableFuture)5 Error (org.folio.rest.jaxrs.model.Error)5 FundDistribution (org.folio.rest.jaxrs.model.FundDistribution)5 Invoice (org.folio.rest.jaxrs.model.Invoice)5 Map (java.util.Map)4