use of org.folio.rest.jaxrs.model.Adjustment in project mod-invoice by folio-org.
the class InvoicesProratedAdjustmentsTest method testUpdateInvoiceWithTwoLinesAddingAdjustmentByLines.
@ParameterizedTest
@ValueSource(strings = { "AMOUNT", "PERCENTAGE" })
public void testUpdateInvoiceWithTwoLinesAddingAdjustmentByLines(Adjustment.Type type) {
logger.info("=== Updating invoice with two lines adding adjustment by lines ===");
// Prepare data "from storage"
Invoice invoice = getMockAsJson(OPEN_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withId(randomUUID().toString());
invoice.getAdjustments().clear();
addMockEntry(INVOICES, invoice);
InvoiceLine invoiceLine1 = getMockInvoiceLine(invoice.getId()).withSubTotal(5d).withInvoiceLineNumber("n-1");
addMockEntry(INVOICE_LINES, invoiceLine1);
InvoiceLine invoiceLine2 = getMockInvoiceLine(invoice.getId()).withSubTotal(15d).withInvoiceLineNumber("n-2");
addMockEntry(INVOICE_LINES, invoiceLine2);
// Prepare request body
Invoice invoiceBody = copyObject(invoice);
invoiceBody.getAdjustments().add(createAdjustment(BY_LINE, type, 9.30d));
// Send update request
verifyPut(String.format(INVOICE_ID_PATH, invoice.getId()), invoiceBody, "", 204);
// Verification
assertThat(getInvoiceUpdates(), hasSize(1));
assertThat(getInvoiceLineUpdates(), hasSize(2));
Invoice invoiceToStorage = getInvoiceUpdates().get(0).mapTo(Invoice.class);
assertThat(invoiceToStorage.getAdjustments(), hasSize(1));
Adjustment invoiceAdjustment = invoiceToStorage.getAdjustments().get(0);
assertThat(invoiceAdjustment.getId(), not(isEmptyOrNullString()));
/*
* Total invoice adjustment value is sum of invoice line adjustments and depends on type:
* "Amount" - this is the same as prorated adjustment value i.e. 9.30$
* "Percentage" - (20 * 9.30) / 100
*/
assertThat(invoiceToStorage.getAdjustmentsTotal(), is(type == AMOUNT ? 9.30d : 1.86d));
// Adjustment value is invoice prorated adjustment's value divided by number of lines but adjustment total amount is calculated based on subTotal
double expectedAdjValue = (type == AMOUNT) ? 4.65d : 0.93;
getInvoiceLineUpdates().stream().map(json -> json.mapTo(InvoiceLine.class)).forEach(line -> {
assertThat(line.getAdjustments(), hasSize(1));
Adjustment adj = line.getAdjustments().get(0);
verifyInvoiceLineAdjustmentCommon(invoiceAdjustment, adj);
assertThat(adj.getValue(), is(expectedAdjValue));
assertThat(line.getAdjustmentsTotal(), is(expectedAdjValue));
});
}
use of org.folio.rest.jaxrs.model.Adjustment in project mod-invoice by folio-org.
the class InvoicesProratedAdjustmentsTest method testUpdateInvoiceWithThreeLinesAddingAmountAdjustmentByAmountWithMissedPenny.
@Test
public void testUpdateInvoiceWithThreeLinesAddingAmountAdjustmentByAmountWithMissedPenny() {
logger.info("=== Updating invoice with zero subTotal and three lines (mixed subTotals) adding adjustment by amount, missing one \"penny\" ===");
// Prepare data "from storage"
Invoice invoice = getMockAsJson(OPEN_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withId(randomUUID().toString());
invoice.getAdjustments().clear();
addMockEntry(INVOICES, invoice);
InvoiceLine invoiceLine1 = getMockInvoiceLine(invoice.getId()).withSubTotal(50d).withInvoiceLineNumber("n-1");
addMockEntry(INVOICE_LINES, invoiceLine1);
InvoiceLine invoiceLine2 = getMockInvoiceLine(invoice.getId()).withSubTotal(15d).withInvoiceLineNumber("n-2");
addMockEntry(INVOICE_LINES, invoiceLine2);
InvoiceLine invoiceLine3 = getMockInvoiceLine(invoice.getId()).withSubTotal(-5d).withInvoiceLineNumber("n-3");
addMockEntry(INVOICE_LINES, invoiceLine3);
// Prepare request body
Invoice invoiceBody = copyObject(invoice);
invoiceBody.getAdjustments().add(createAdjustment(BY_AMOUNT, AMOUNT, 5d));
// Send update request
verifyPut(String.format(INVOICE_ID_PATH, invoice.getId()), invoiceBody, "", 204);
// Verification
assertThat(getInvoiceUpdates(), hasSize(1));
assertThat(getInvoiceLineUpdates(), hasSize(3));
Invoice invoiceToStorage = getInvoiceUpdates().get(0).mapTo(Invoice.class);
assertThat(invoiceToStorage.getAdjustments(), hasSize(1));
// Prorated adj value + non prorated adj of first line
assertThat(invoiceToStorage.getAdjustmentsTotal(), is(5d));
Adjustment invoiceAdjustment = invoiceToStorage.getAdjustments().get(0);
assertThat(invoiceAdjustment.getId(), not(isEmptyOrNullString()));
// 5 * 50 / 70 = 3,57(1428571428571)
verifyAdjustmentValue(invoiceLine1.getId(), 3.57d);
// 5 * 15 / 70 = 1,07(1428571428571)
verifyAdjustmentValue(invoiceLine2.getId(), 1.07d);
// 5 * 5 / 70 = 0,35(7142857142857) + 0.01
verifyAdjustmentValue(invoiceLine3.getId(), 0.36d);
}
use of org.folio.rest.jaxrs.model.Adjustment in project mod-invoice by folio-org.
the class InvoicesProratedAdjustmentsTest method testUpdateInvoiceWithOneLineMakingAdjustmentNotProrated.
@ParameterizedTest
@CsvSource({ "BY_AMOUNT, AMOUNT", "BY_AMOUNT, PERCENTAGE", "BY_LINE, AMOUNT", "BY_LINE, PERCENTAGE", "BY_QUANTITY, AMOUNT", "BY_QUANTITY, PERCENTAGE" })
public void testUpdateInvoiceWithOneLineMakingAdjustmentNotProrated(Adjustment.Prorate prorate, Adjustment.Type type) {
logger.info("=== Updating invoice with one line and one prorated adjustment - making adjustment \"Not prorated\" ===");
// Prepare data "from storage"
Invoice invoice = getMockAsJson(OPEN_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withId(randomUUID().toString());
final double adjValue = 15d;
Adjustment adjustment = createAdjustment(prorate, type, adjValue).withId(randomUUID().toString());
invoice.setAdjustments(Collections.singletonList(adjustment));
addMockEntry(INVOICES, invoice);
InvoiceLine invoiceLine = getMockInvoiceLine(invoice.getId()).withSubTotal(25d).withAdjustments(Collections.singletonList(copyObject(adjustment).withAdjustmentId(adjustment.getId()).withId(null)));
addMockEntry(INVOICE_LINES, invoiceLine);
// Send update request making adjustment "Not prorated"
Invoice body = copyObject(invoice);
body.getAdjustments().get(0).setProrate(NOT_PRORATED);
verifyPut(String.format(INVOICE_ID_PATH, invoice.getId()), body, "", 204);
// Verification
assertThat(getInvoiceUpdates(), hasSize(1));
assertThat(getInvoiceLineUpdates(), hasSize(1));
/*
* Calculated adjustment value depends of type:
* "Amount" - this is the same as adjustment value
* "Percentage" - this is the percentage of subTotal i.e. 15% of 25$ = 3.75$
*/
double expectedAdjTotal = (type == AMOUNT) ? adjValue : 3.75d;
Invoice invoiceToStorage = getInvoiceUpdates().get(0).mapTo(Invoice.class);
assertThat(invoiceToStorage.getAdjustments(), hasSize(1));
assertThat(invoiceToStorage.getAdjustmentsTotal(), is(expectedAdjTotal));
Adjustment invoiceAdjustment = invoiceToStorage.getAdjustments().get(0);
assertThat(invoiceAdjustment.getId(), not(isEmptyOrNullString()));
// Adjustments should be deleted
InvoiceLine lineToStorage = getInvoiceLineUpdates().get(0).mapTo(InvoiceLine.class);
assertThat(lineToStorage.getAdjustments(), empty());
assertThat(lineToStorage.getAdjustmentsTotal(), is(0d));
}
use of org.folio.rest.jaxrs.model.Adjustment in project mod-invoice by folio-org.
the class BudgetExpenseClassTest method shouldThrowExceptionWithBudgetExpenseClassNotFoundCodeWhenCheckExpenseClassesWithoutExpenseClasses.
@Test
void shouldThrowExceptionWithBudgetExpenseClassNotFoundCodeWhenCheckExpenseClassesWithoutExpenseClasses() {
String notAssignedExpenseClassId = UUID.randomUUID().toString();
String activeExpenseClassId = UUID.randomUUID().toString();
FundDistribution fundDistributionWithActiveExpenseClass = new FundDistribution().withFundId(UUID.randomUUID().toString()).withExpenseClassId(activeExpenseClassId);
FundDistribution fundDistributionWithNotAssignedExpenseClass = new FundDistribution().withFundId(UUID.randomUUID().toString()).withExpenseClassId(notAssignedExpenseClassId);
FundDistribution fundDistributionWithoutExpenseClass = new FundDistribution().withFundId(UUID.randomUUID().toString());
InvoiceLine invoiceLine = new InvoiceLine().withFundDistributions(Arrays.asList(fundDistributionWithActiveExpenseClass, fundDistributionWithoutExpenseClass));
List<InvoiceLine> invoiceLines = Collections.singletonList(invoiceLine);
Adjustment adjustment = new Adjustment().withFundDistributions(Collections.singletonList(fundDistributionWithNotAssignedExpenseClass));
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());
Fund noExpenseClassFund = new Fund().withCode("no expense class fund");
ExpenseClass notAssignedExpenseClass = new ExpenseClass().withName("not assigned class");
List<InvoiceWorkflowDataHolder> holders = new ArrayList<>();
InvoiceWorkflowDataHolder holder1 = new InvoiceWorkflowDataHolder().withInvoice(invoice).withInvoiceLine(invoiceLine).withFundDistribution(fundDistributionWithActiveExpenseClass).withBudget(new Budget().withFundId(UUID.randomUUID().toString())).withExpenseClass(new ExpenseClass().withId(activeExpenseClassId));
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(fundDistributionWithNotAssignedExpenseClass).withExpenseClass(notAssignedExpenseClass).withBudget(new Budget().withFundId(UUID.randomUUID().toString())).withFund(noExpenseClassFund);
holders.add(holder1);
holders.add(holder2);
holders.add(holder3);
when(restClient.get(any(RequestEntry.class), any(), any())).thenAnswer(invocation -> {
RequestEntry requestEntry = invocation.getArgument(0);
List<BudgetExpenseClass> budgetExpenseClasses = requestEntry.buildEndpoint().contains(notAssignedExpenseClassId) ? Collections.emptyList() : Collections.singletonList(active);
return CompletableFuture.completedFuture(new BudgetExpenseClassCollection().withBudgetExpenseClasses(budgetExpenseClasses).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(BUDGET_EXPENSE_CLASS_NOT_FOUND.getCode(), error.getCode());
String expenseClassNameFromError = error.getParameters().stream().filter(parameter -> parameter.getKey().equals(EXPENSE_CLASS_NAME)).findFirst().get().getValue();
assertEquals(notAssignedExpenseClass.getName(), expenseClassNameFromError);
String fundCodeFromError = error.getParameters().stream().filter(parameter -> parameter.getKey().equals(FUND_CODE)).findFirst().get().getValue();
assertEquals(noExpenseClassFund.getCode(), fundCodeFromError);
}
use of org.folio.rest.jaxrs.model.Adjustment in project mod-invoice by folio-org.
the class InvoicesApiTest method testPutInvoiceByIdChangeStatusToPayedActiveBudgetNotFound.
@Test
void testPutInvoiceByIdChangeStatusToPayedActiveBudgetNotFound() {
logger.info("=== Test Put Invoice By Id, Current fiscal year not found ===");
Invoice reqData = getMockAsJson(APPROVED_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withStatus(Invoice.Status.PAID);
String id = reqData.getId();
reqData.setStatus(Status.PAID);
InvoiceLine invoiceLine = getMockAsJson(INVOICE_LINE_WITH_APPROVED_INVOICE_SAMPLE_PATH).mapTo(InvoiceLine.class);
invoiceLine.setId(UUID.randomUUID().toString());
invoiceLine.setInvoiceId(reqData.getId());
invoiceLine.getFundDistributions().get(0).withFundId(FUND_ID_WITH_NOT_ACTIVE_BUDGET);
invoiceLine.getFundDistributions().forEach(fundDistribution -> {
Fund fund = new Fund().withId(fundDistribution.getFundId()).withExternalAccountNo("externalNo").withLedgerId(EXISTING_LEDGER_ID);
addMockEntry(FUNDS, fund);
});
reqData.getAdjustments().stream().flatMap(adjustment -> adjustment.getFundDistributions().stream()).map(FundDistribution::getFundId).distinct().forEach(fundId -> {
Fund fund = new Fund().withId(fundId).withExternalAccountNo("externalNo").withLedgerId(EXISTING_LEDGER_ID);
addMockEntry(FUNDS, fund);
});
addMockEntry(LEDGERS, JsonObject.mapFrom(new Ledger().withId(EXISTING_LEDGER_ID).withRestrictEncumbrance(true)));
addMockEntry(INVOICE_LINES, JsonObject.mapFrom(invoiceLine));
prepareMockVoucher(id);
Errors errors = verifyPut(String.format(INVOICE_ID_PATH, id), JsonObject.mapFrom(reqData), APPLICATION_JSON, 404).as(Errors.class);
assertThat(getRqRsEntries(HttpMethod.GET, INVOICE_LINES), hasSize(1));
assertThat(getRqRsEntries(HttpMethod.GET, FINANCE_TRANSACTIONS), hasSize(0));
assertThat(getRqRsEntries(HttpMethod.GET, FUNDS), hasSize(1));
assertThat(getRqRsEntries(HttpMethod.POST, FINANCE_PAYMENTS), hasSize(0));
assertThat(getRqRsEntries(HttpMethod.POST, FINANCE_CREDITS), hasSize(0));
assertThat(errors.getErrors(), hasSize(1));
Error error = errors.getErrors().get(0);
assertThat(error.getCode(), equalTo(BUDGET_NOT_FOUND.getCode()));
}
Aggregations