diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index 7b82478eb..1724ed100 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -11,7 +11,7 @@
"pathPattern": "/invoice/invoices",
"permissionsRequired": ["invoice.invoices.collection.get"],
"permissionsDesired": [
- "invoices.bypass-acquisition-units"
+ "invoices.acquisition-units.bypass.execute"
],
"modulePermissions": [
"invoice-storage.invoices.collection.get",
@@ -30,7 +30,8 @@
"invoice-storage.invoice-number.get",
"acquisitions-units-storage.units.collection.get",
"acquisitions-units-storage.memberships.collection.get",
- "organizations-storage.organizations.item.get"
+ "organizations-storage.organizations.item.get",
+ "finance.ledgers.current-fiscal-year.item.get"
]
},
{
@@ -40,7 +41,7 @@
"invoice.invoices.item.get"
],
"permissionsDesired": [
- "invoices.bypass-acquisition-units"
+ "invoices.acquisition-units.bypass.execute"
],
"modulePermissions": [
"invoice-storage.invoices.item.get",
@@ -55,11 +56,11 @@
"permissionsRequired": ["invoice.invoices.item.put"],
"permissionsDesired": [
"invoices.acquisitions-units-assignments.manage",
- "invoices.fiscal-year.update",
- "invoice.item.approve",
- "invoice.item.pay",
- "invoice.item.cancel",
- "invoices.bypass-acquisition-units"
+ "invoices.fiscal-year.update.execute",
+ "invoice.item.approve.execute",
+ "invoice.item.pay.execute",
+ "invoice.item.cancel.execute",
+ "invoices.acquisition-units.bypass.execute"
],
"modulePermissions": [
"configuration.entries.collection.get",
@@ -68,7 +69,7 @@
"invoice-storage.invoices.item.get",
"invoice-storage.invoice-lines.item.put",
"invoice-storage.invoice-lines.collection.get",
- "orders.bypass-acquisition-units",
+ "orders.acquisition-units.bypass.execute",
"orders.collection.get",
"orders.po-lines.collection.get",
"orders.po-lines.item.put",
@@ -82,7 +83,7 @@
"voucher-storage.voucher-number.get",
"acquisitions-units-storage.units.collection.get",
"acquisitions-units-storage.memberships.collection.get",
- "finance.transactions.batch",
+ "finance.transactions.batch.execute",
"finance.transactions.collection.get",
"finance.funds.item.get",
"finance.funds.collection.get",
@@ -118,7 +119,7 @@
"pathPattern": "/invoice/invoice-lines",
"permissionsRequired": ["invoice.invoice-lines.collection.get"],
"permissionsDesired": [
- "invoices.bypass-acquisition-units"
+ "invoices.acquisition-units.bypass.execute"
],
"modulePermissions": [
"invoice-storage.invoice-lines.collection.get",
@@ -160,7 +161,7 @@
"pathPattern": "/invoice/invoice-lines/{id}",
"permissionsRequired": ["invoice.invoice-lines.item.get"],
"permissionsDesired": [
- "invoices.bypass-acquisition-units"
+ "invoices.acquisition-units.bypass.execute"
],
"modulePermissions": [
"invoice-storage.invoice-lines.item.get",
@@ -178,7 +179,7 @@
"pathPattern": "/invoice/invoice-lines/{id}",
"permissionsRequired": ["invoice.invoice-lines.item.put"],
"permissionsDesired": [
- "invoices.bypass-acquisition-units"
+ "invoices.acquisition-units.bypass.execute"
],
"modulePermissions": [
"invoice-storage.invoice-lines.item.put",
@@ -716,19 +717,22 @@
"description": "Delete an existing Invoice"
},
{
- "permissionName": "invoice.item.approve",
+ "permissionName": "invoice.item.approve.execute",
"displayName": "Invoice - approve an existing Invoice",
- "description": "Approve an existing Invoice"
+ "description": "Approve an existing Invoice",
+ "replaces": ["invoice.item.approve"]
},
{
- "permissionName": "invoice.item.pay",
+ "permissionName": "invoice.item.pay.execute",
"displayName": "Invoice - pay an existing Invoice",
- "description": "Pay an existing Invoice"
+ "description": "Pay an existing Invoice",
+ "replaces": ["invoice.item.pay"]
},
{
- "permissionName": "invoice.item.cancel",
+ "permissionName": "invoice.item.cancel.execute",
"displayName": "Invoice - cancel an existing Invoice",
- "description": "Cancel an existing Invoice"
+ "description": "Cancel an existing Invoice",
+ "replaces": ["invoice.item.cancel"]
},
{
"permissionName": "invoice.invoice-lines.collection.get",
@@ -917,9 +921,10 @@
]
},
{
- "permissionName": "invoices.bypass-acquisition-units",
+ "permissionName": "invoices.acquisition-units.bypass.execute",
"displayName": "Bypass acquisition units checks",
- "description": "Backend internal permission to bypass invoice acquisition units checks"
+ "description": "Backend internal permission to bypass invoice acquisition units checks",
+ "replaces": ["invoices.bypass-acquisition-units"]
},
{
"permissionName": "invoices.acquisitions-units-assignments.assign",
@@ -941,9 +946,10 @@
]
},
{
- "permissionName": "invoices.fiscal-year.update",
+ "permissionName": "invoices.fiscal-year.update.execute",
"displayName": "Invoice - update fiscal year",
- "description": "Update a fiscal year"
+ "description": "Update a fiscal year",
+ "replaces": ["invoices.fiscal-year.update"]
},
{
"permissionName": "batch-groups.collection.get",
@@ -1057,9 +1063,9 @@
"invoice.invoices.item.post",
"invoice.invoices.item.put",
"invoice.invoices.item.delete",
- "invoice.item.approve",
- "invoice.item.pay",
- "invoice.item.cancel",
+ "invoice.item.approve.execute",
+ "invoice.item.pay.execute",
+ "invoice.item.cancel.execute",
"invoice.invoice-lines.collection.get",
"invoice.invoice-lines.item.get",
"invoice.invoice-lines.item.post",
diff --git a/pom.xml b/pom.xml
index 4cf7864be..b7278f2fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,10 +24,11 @@
35.2.0
2.3.3
- 4.5.4
+ 4.5.9
5.4.0
5.10.0
- 6.1.5
+ 2.13.2
+ 6.1.12
2.1.0
3.25.3
1.9.21.1
@@ -89,17 +90,14 @@
io.vertx
vertx-core
- ${vertx.version}
io.vertx
vertx-web
- ${vertx.version}
io.vertx
vertx-unit
- ${vertx.version}
test
@@ -110,24 +108,22 @@
org.apache.sshd
sshd-sftp
- 2.9.3
+ ${sshd.version}
org.apache.sshd
sshd-spring-sftp
- 2.8.0
+ ${sshd.version}
io.vertx
vertx-rx-java
- ${vertx.version}
provided
io.vertx
vertx-junit5
- ${vertx.version}
test
@@ -151,13 +147,11 @@
org.springframework
spring-test
- ${spring.version}
test
org.springframework
spring-context
- ${spring.version}
org.jvnet.jaxb2_commons
@@ -232,25 +226,21 @@
org.testcontainers
postgresql
- ${testcontainers.version}
test
org.testcontainers
kafka
- ${testcontainers.version}
test
org.testcontainers
testcontainers
- ${testcontainers.version}
test
org.testcontainers
junit-jupiter
- 1.19.0
test
@@ -778,6 +768,27 @@
import
pom
+
+ org.springframework
+ spring-framework-bom
+ ${spring.version}
+ import
+ pom
+
+
+ io.vertx
+ vertx-stack-depchain
+ ${vertx.version}
+ import
+ pom
+
+
+ org.testcontainers
+ testcontainers-bom
+ ${testcontainers.version}
+ import
+ pom
+
diff --git a/ramls/acq-models b/ramls/acq-models
index a46a8fb8c..c1f931704 160000
--- a/ramls/acq-models
+++ b/ramls/acq-models
@@ -1 +1 @@
-Subproject commit a46a8fb8ce704fd39acc5774fd01d2d8d48236cd
+Subproject commit c1f931704fc6d2dedfc671e308b27f56499750a7
diff --git a/src/main/java/org/folio/invoices/utils/AcqDesiredPermissions.java b/src/main/java/org/folio/invoices/utils/AcqDesiredPermissions.java
index caa1d25cf..604cf20c8 100644
--- a/src/main/java/org/folio/invoices/utils/AcqDesiredPermissions.java
+++ b/src/main/java/org/folio/invoices/utils/AcqDesiredPermissions.java
@@ -8,11 +8,11 @@
public enum AcqDesiredPermissions {
ASSIGN("invoices.acquisitions-units-assignments.assign"),
MANAGE("invoices.acquisitions-units-assignments.manage"),
- APPROVE("invoice.item.approve"),
- PAY("invoice.item.pay"),
- CANCEL("invoice.item.cancel"),
- FISCAL_YEAR_UPDATE("invoices.fiscal-year.update"),
- BYPASS_ACQ_UNITS("invoices.bypass-acquisition-units");
+ APPROVE("invoice.item.approve.execute"),
+ PAY("invoice.item.pay.execute"),
+ CANCEL("invoice.item.cancel.execute"),
+ FISCAL_YEAR_UPDATE("invoices.fiscal-year.update.execute"),
+ BYPASS_ACQ_UNITS("invoices.acquisition-units.bypass.execute");
private String permission;
private static final List values;
diff --git a/src/main/java/org/folio/services/adjusment/AdjustmentsService.java b/src/main/java/org/folio/services/adjusment/AdjustmentsService.java
index 8a30538f2..2d6f94d14 100644
--- a/src/main/java/org/folio/services/adjusment/AdjustmentsService.java
+++ b/src/main/java/org/folio/services/adjusment/AdjustmentsService.java
@@ -31,6 +31,7 @@
import io.vertx.core.json.JsonObject;
public class AdjustmentsService {
+
private final Logger logger = LogManager.getLogger(this.getClass());
public static final Predicate NOT_PRORATED_ADJUSTMENTS_PREDICATE = adj -> adj.getProrate() == NOT_PRORATED;
public static final Predicate PRORATED_ADJUSTMENTS_PREDICATE = NOT_PRORATED_ADJUSTMENTS_PREDICATE.negate();
@@ -65,7 +66,7 @@ public List applyProratedAdjustments(List lines, Invoi
updatedLines.addAll(applyProratedAdjustmentByLines(adjustment, lines, currencyUnit));
break;
case BY_AMOUNT:
- updatedLines.addAll(applyProratedAdjustmentByAmount(adjustment, lines, currencyUnit));
+ updatedLines.addAll(applyAdjustmentsAndUpdateLines(adjustment, lines, currencyUnit));
break;
case BY_QUANTITY:
updatedLines.addAll(applyProratedAdjustmentByQuantity(adjustment, lines, currencyUnit));
@@ -80,6 +81,15 @@ public List applyProratedAdjustments(List lines, Invoi
.collect(toList());
}
+ private List applyAdjustmentsAndUpdateLines(Adjustment adjustment, List lines,
+ CurrencyUnit currencyUnit) {
+ if (adjustment.getRelationToTotal() == Adjustment.RelationToTotal.INCLUDED_IN) {
+ return applyProratedAmountTypeIncludedInAdjustments(adjustment, lines, currencyUnit);
+ } else {
+ return applyProratedAdjustmentByAmount(adjustment, lines, currencyUnit);
+ }
+ }
+
public void processProratedAdjustments(List lines, Invoice invoice) {
List proratedAdjustments = getProratedAdjustments(invoice);
@@ -88,7 +98,6 @@ public void processProratedAdjustments(List lines, Invoice invoice)
// Apply prorated adjustments to each invoice line
applyProratedAdjustments(lines, invoice);
-
}
/**
@@ -99,10 +108,10 @@ public void processProratedAdjustments(List lines, Invoice invoice)
void filterDeletedAdjustments(List proratedAdjustments, List invoiceLines) {
List adjIds = proratedAdjustments.stream()
.map(Adjustment::getId)
- .collect(toList());
+ .toList();
invoiceLines.forEach(line -> line.getAdjustments()
- .removeIf(adj -> Objects.nonNull(adj.getAdjustmentId()) && !adjIds.contains(adj.getAdjustmentId())));
+ .removeIf(adj -> Objects.nonNull(adj.getAdjustmentId()) && !adjIds.contains(adj.getAdjustmentId())));
}
/**
@@ -179,7 +188,7 @@ private List applyAmountTypeProratedAdjustments(Adjustment adjustme
int remainderSignum = remainder.signum();
MonetaryAmount smallestUnit = getSmallestUnit(expectedAdjustmentTotal, remainderSignum);
- for (ListIterator iterator = getIterator(lines, remainderSignum); isIteratorHasNext(iterator, remainderSignum);) {
+ for (ListIterator iterator = getIterator(lines, remainderSignum); isIteratorHasNext(iterator, remainderSignum); ) {
final InvoiceLine line = iteratorNext(iterator, remainderSignum);
MonetaryAmount amount = lineIdAdjustmentValueMap.get(line.getId());
@@ -190,8 +199,7 @@ private List applyAmountTypeProratedAdjustments(Adjustment adjustme
}
Adjustment proratedAdjustment = prepareAdjustmentForLine(adjustment);
- proratedAdjustment.setValue(amount.getNumber()
- .doubleValue());
+ proratedAdjustment.setValue(amount.getNumber().doubleValue());
if (addAdjustmentToLine(line, proratedAdjustment)) {
updatedLines.add(line);
}
@@ -200,13 +208,40 @@ private List applyAmountTypeProratedAdjustments(Adjustment adjustme
return updatedLines;
}
+ private List applyProratedAmountTypeIncludedInAdjustments(Adjustment adjustment, List lines,
+ CurrencyUnit currencyUnit) {
+ List updatedLines = new ArrayList<>();
+ for (InvoiceLine line : lines) {
+ if (invoiceLineWasAdjustedById(adjustment, line)) {
+ continue;
+ }
+ MonetaryAmount lineSubtotal = Money.of(line.getSubTotal(), currencyUnit);
+ MonetaryAmount amountAdjustmentValue = lineSubtotal.multiply(adjustment.getValue())
+ .divide(Money.of(100, currencyUnit).add(Money.of(adjustment.getValue(), currencyUnit)).getNumber().doubleValue())
+ .with(Monetary.getDefaultRounding());
+ Adjustment preparedAdjustment = prepareAdjustmentForLine(adjustment.withType(Adjustment.Type.AMOUNT))
+ .withValue(amountAdjustmentValue.getNumber().doubleValue());
+ line.withSubTotal(lineSubtotal.subtract(amountAdjustmentValue).getNumber().doubleValue());
+ if (addAdjustmentToLine(line, preparedAdjustment)) {
+ updatedLines.add(line);
+ }
+ }
+ return updatedLines;
+ }
+
+ private static boolean invoiceLineWasAdjustedById(Adjustment adjustment, InvoiceLine line) {
+ return Objects.nonNull(adjustment.getId()) && line.getAdjustments().stream()
+ .map(Adjustment::getAdjustmentId)
+ .filter(Objects::nonNull)
+ .anyMatch(lineAdjustmentId -> lineAdjustmentId.equals(adjustment.getId()));
+ }
+
/**
* Each invoiceLine gets a portion of the amount proportionate to the invoiceLine's contribution to the invoice subTotal.
* Prorated percentage adjustments of this type aren't split but rather each invoiceLine gets an adjustment of that percentage
*/
private List applyProratedAdjustmentByAmount(Adjustment adjustment, List lines,
CurrencyUnit currencyUnit) {
-
if (adjustment.getType() == Adjustment.Type.PERCENTAGE) {
adjustment = convertToAmountAdjustment(adjustment, lines, currencyUnit);
}
@@ -232,7 +267,6 @@ private BiFunction prorateByAmountF
*/
private List applyProratedAdjustmentByQuantity(Adjustment adjustment, List lines,
CurrencyUnit currencyUnit) {
-
if (adjustment.getType() == Adjustment.Type.PERCENTAGE) {
return applyPercentageAdjustmentsByQuantity(adjustment, lines, currencyUnit);
}
@@ -285,5 +319,4 @@ private InvoiceLine iteratorNext(ListIterator iterator, int remaind
private BiFunction prorateByLines(List lines) {
return (amount, line) -> amount.divide(lines.size()).with(Monetary.getDefaultRounding());
}
-
}
diff --git a/src/main/java/org/folio/services/invoice/InvoicePaymentService.java b/src/main/java/org/folio/services/invoice/InvoicePaymentService.java
index f5aa438a9..9ac84dc93 100644
--- a/src/main/java/org/folio/services/invoice/InvoicePaymentService.java
+++ b/src/main/java/org/folio/services/invoice/InvoicePaymentService.java
@@ -10,19 +10,15 @@
import java.util.List;
import java.util.Set;
import java.util.Map;
-import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.folio.InvoiceWorkflowDataHolderBuilder;
-import org.folio.invoices.rest.exceptions.HttpException;
import org.folio.rest.acq.model.orders.CompositePoLine;
import org.folio.rest.core.models.RequestContext;
-import org.folio.rest.jaxrs.model.FundDistribution;
import org.folio.rest.jaxrs.model.Invoice;
import org.folio.rest.jaxrs.model.InvoiceLine;
-import org.folio.services.finance.fiscalyear.CurrentFiscalYearService;
import org.folio.services.finance.transaction.PaymentCreditWorkflowService;
import org.folio.services.order.OrderLineService;
import org.folio.services.voucher.VoucherService;
@@ -30,7 +26,6 @@
import com.google.common.annotations.VisibleForTesting;
-import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
public class InvoicePaymentService {
@@ -43,10 +38,7 @@ public class InvoicePaymentService {
private VoucherService voucherService;
@Autowired
private OrderLineService orderLineService;
- @Autowired
- private CurrentFiscalYearService currentFiscalYearService;
- public static final String INVOICE_LINE_MUST_HAVE_FUND = "The invoice line must contain the fund for payment";
protected static final Set PO_LINE_PAYMENT_IGNORED_STATUSES =
EnumSet.of(ONGOING, PAYMENT_NOT_REQUIRED);
@@ -62,41 +54,17 @@ public Future payInvoice(Invoice invoice, List invoiceLines,
invoice.setPaymentDate(invoice.getMetadata().getUpdatedDate());
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)))
+ .compose(v -> Future.join(payPoLines(invoiceLines, requestContext), voucherService.payInvoiceVoucher(invoice.getId(), requestContext)))
.mapEmpty();
}
- private Future updatePoLinesStatus(Invoice invoice, List invoiceLines, RequestContext requestContext) {
- if (StringUtils.isBlank(invoice.getFiscalYearId())) {
- return updatePoLinesToPaidStatus(invoiceLines, requestContext);
- }
-
- String fundID = invoiceLines.stream()
- .flatMap(invoiceLine -> invoiceLine.getFundDistributions().stream())
- .map(FundDistribution::getFundId)
- .findFirst()
- .orElse(null);
-
- if (StringUtils.isNotBlank(fundID)) {
- return currentFiscalYearService.getCurrentFiscalYearByFund(fundID, requestContext)
- .compose(currentFiscalYear -> {
- if (Objects.equals(currentFiscalYear.getId(), invoice.getFiscalYearId())) {
- return updatePoLinesToPaidStatus(invoiceLines, requestContext);
- } else {
- return Future.succeededFuture();
- }
- });
- }
- return Future.failedFuture(new HttpException(400, INVOICE_LINE_MUST_HAVE_FUND));
- }
-
/**
* Updates payment status of the associated PO Lines.
*
* @param invoiceLines the invoice lines to be paid
* @return CompletableFuture that indicates when transition is completed
*/
- private Future updatePoLinesToPaidStatus(List invoiceLines, RequestContext requestContext) {
+ private Future payPoLines(List invoiceLines, RequestContext requestContext) {
Map> poLineIdInvoiceLinesMap = groupInvoiceLinesByPoLineId(invoiceLines);
return fetchPoLines(poLineIdInvoiceLinesMap, requestContext)
.map(this::updatePoLinesPaymentStatus)
diff --git a/src/test/java/org/folio/rest/impl/ApiTestBase.java b/src/test/java/org/folio/rest/impl/ApiTestBase.java
index 82fca1871..3807c2cc1 100644
--- a/src/test/java/org/folio/rest/impl/ApiTestBase.java
+++ b/src/test/java/org/folio/rest/impl/ApiTestBase.java
@@ -98,12 +98,12 @@ public class ApiTestBase {
private static boolean runningOnOwn;
public static final List permissionsList = Arrays.asList(
- "invoice.item.approve",
- "invoice.item.pay",
+ "invoice.item.approve.execute",
+ "invoice.item.pay.execute",
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
"invoices.acquisitions-units-assignments.assign",
- "invoices.fiscal-year.update"
+ "invoices.fiscal-year.update.execute"
);
public static final JsonArray permissionsArray = new JsonArray(permissionsList);
@@ -114,7 +114,7 @@ public class ApiTestBase {
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
"invoices.acquisitions-units-assignments.assign",
- "invoices.fiscal-year.update"
+ "invoices.fiscal-year.update.execute"
);
public static final JsonArray permissionsWithoutApproveAndPayArray = new JsonArray(permissionsWithoutApproveAndPayList);
@@ -122,11 +122,11 @@ public class ApiTestBase {
public static final Header X_OKAPI_PERMISSION_WITHOUT_PAY_APPROVE = new Header(UserPermissionsUtil.OKAPI_HEADER_PERMISSIONS, permissionsWithoutApproveAndPayJsonArrayString);
public static final List permissionsWithoutPaidList = Arrays.asList(
- "invoice.item.approve",
+ "invoice.item.approve.execute",
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
"invoices.acquisitions-units-assignments.assign",
- "invoices.fiscal-year.update"
+ "invoices.fiscal-year.update.execute"
);
public static final JsonArray permissionsWithoutPaidArray = new JsonArray(permissionsWithoutPaidList);
diff --git a/src/test/java/org/folio/rest/impl/InvoiceLinesProratedAdjustmentsTest.java b/src/test/java/org/folio/rest/impl/InvoiceLinesProratedAdjustmentsTest.java
index fafaa0397..95011347a 100644
--- a/src/test/java/org/folio/rest/impl/InvoiceLinesProratedAdjustmentsTest.java
+++ b/src/test/java/org/folio/rest/impl/InvoiceLinesProratedAdjustmentsTest.java
@@ -11,6 +11,9 @@
import static org.folio.rest.impl.MockServer.getInvoiceLineCreations;
import static org.folio.rest.impl.MockServer.getInvoiceLineUpdates;
import static org.folio.rest.impl.MockServer.getInvoiceUpdates;
+import static org.folio.rest.jaxrs.model.Adjustment.Prorate.BY_AMOUNT;
+import static org.folio.rest.jaxrs.model.Adjustment.RelationToTotal.INCLUDED_IN;
+import static org.folio.rest.jaxrs.model.Adjustment.Type.PERCENTAGE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
@@ -19,6 +22,7 @@
import io.vertx.junit5.VertxExtension;
import java.util.Collections;
+import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -27,6 +31,7 @@
import org.folio.rest.jaxrs.model.InvoiceLine;
import org.folio.rest.jaxrs.model.InvoiceLineCollection;
import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@@ -182,6 +187,82 @@ public void testDeleteLineForInvoiceWithOneAdj(Adjustment.Prorate prorate, Adjus
assertThat(lineAdjustment.getValue(), is(expectedAdjValue));
}
+ @Test
+ public void testCreateInvoiceWithOnePercentageTypeByAmountProrateIncludedByTotalAdjustment() {
+ logger.info("=== Creating invoice with one adjustment by amount prorate included by total ===");
+
+ // Prepare data "from storage"
+ Invoice invoice = getMockAsJson(OPEN_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withId(randomUUID().toString());
+ Adjustment invoiceAdjustment = new Adjustment()
+ .withId(UUID.randomUUID().toString())
+ .withDescription("VAT")
+ .withProrate(BY_AMOUNT)
+ .withType(PERCENTAGE)
+ .withRelationToTotal(INCLUDED_IN)
+ .withValue(7d);
+ invoice.withAdjustments(Collections.singletonList(invoiceAdjustment));
+ addMockEntry(INVOICES, invoice);
+
+ // Prepare request body
+ InvoiceLine invoiceLineBody = getMockInvoiceLine(invoice.getId()).withAdjustmentsTotal(0d).withSubTotal(30d).withQuantity(1);
+
+ // Send create request
+ InvoiceLine invoiceLine = verifySuccessPost(INVOICE_LINES_PATH, invoiceLineBody).as(InvoiceLine.class);
+
+ // Verification
+ assertThat(getInvoiceLineUpdates(), Matchers.hasSize(0));
+ assertThat(getInvoiceUpdates(), Matchers.hasSize(1));
+ compareRecordWithSentToStorage(invoiceLine);
+
+ assertThat(invoiceLine.getAdjustments(), hasSize(1));
+ assertThat(invoiceLine.getAdjustmentsTotal(), is(0d));
+ assertThat(invoiceLine.getSubTotal(), is(28.04d));
+
+ Adjustment lineAdjustment = invoiceLine.getAdjustments().get(0);
+ verifyInvoiceLineAdjustmentCommon(invoiceAdjustment, lineAdjustment);
+ assertThat(lineAdjustment.getValue(), is(1.96d));
+ }
+
+ @Test
+ public void testDeleteInvoiceWithOnePercentageTypeByAmountProrateIncludedByTotalAdjustment() {
+ logger.info("=== Deleting invoice with one adjustment by amount prorate included by total ===");
+
+ // Prepare data "from storage"
+ Invoice invoice = getMockAsJson(OPEN_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withId(randomUUID().toString());
+ Adjustment invoiceAdjustment = new Adjustment()
+ .withId(UUID.randomUUID().toString())
+ .withDescription("VAT")
+ .withProrate(BY_AMOUNT)
+ .withType(PERCENTAGE)
+ .withRelationToTotal(INCLUDED_IN)
+ .withValue(7d);
+ invoice.withAdjustments(Collections.singletonList(invoiceAdjustment));
+ addMockEntry(INVOICES, invoice);
+
+ InvoiceLine line1 = getMockInvoiceLine(invoice.getId()).withAdjustmentsTotal(0d).withSubTotal(30d).withQuantity(1);
+ addMockEntry(INVOICE_LINES, line1);
+ InvoiceLine line2 = getMockInvoiceLine(invoice.getId()).withAdjustmentsTotal(0d).withSubTotal(30d).withQuantity(1);
+ addMockEntry(INVOICE_LINES, line2);
+
+ // Send delete request
+ verifyDeleteResponse(String.format(INVOICE_LINE_ID_PATH, line2.getId()), "", 204);
+
+ // Verification
+ assertThat(getInvoiceLineUpdates(), Matchers.hasSize(1));
+ assertThat(getInvoiceUpdates(), Matchers.hasSize(1));
+
+ InvoiceLine lineToStorage = getLineToStorageById(line1.getId());
+ assertThat(lineToStorage.getAdjustments(), hasSize(1));
+
+ assertThat(lineToStorage.getAdjustments(), hasSize(1));
+ assertThat(lineToStorage.getAdjustmentsTotal(), is(0d));
+ assertThat(lineToStorage.getSubTotal(), is(28.04));
+
+ Adjustment lineAdjustment = lineToStorage.getAdjustments().get(0);
+ verifyInvoiceLineAdjustmentCommon(invoiceAdjustment, lineAdjustment);
+ assertThat(lineAdjustment.getValue(), is(1.96d));
+ }
+
private InvoiceLine getLineToStorageById(String invoiceLineId) {
return getInvoiceLineUpdates().stream()
.filter(line -> invoiceLineId.equals(line.getString("id")))
diff --git a/src/test/java/org/folio/rest/impl/InvoicesApiTest.java b/src/test/java/org/folio/rest/impl/InvoicesApiTest.java
index a19f03c2c..f008c9aad 100644
--- a/src/test/java/org/folio/rest/impl/InvoicesApiTest.java
+++ b/src/test/java/org/folio/rest/impl/InvoicesApiTest.java
@@ -2279,22 +2279,6 @@ void testUpdateInvoiceTransitionToPaidPoLineIdNotSpecified() {
assertThatVoucherPaid();
}
- @Test
- void testShouldFailInvoiceLineDoesNotHaveFund() {
- logger.info("=== Test should fail because invoice line doesn't have fund ===");
-
- Invoice reqData = getMockAsJson(APPROVED_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withStatus(Invoice.Status.PAID);
- String id = reqData.getId();
-
- prepareMockVoucher(id);
- InvoiceLine invoiceLine = getMinimalContentInvoiceLine(id);
- addMockEntry(INVOICE_LINES, JsonObject.mapFrom(invoiceLine));
-
- String jsonBody = JsonObject.mapFrom(reqData).encode();
- Headers headers = prepareHeaders(X_OKAPI_URL, X_OKAPI_TENANT, X_OKAPI_TOKEN, X_OKAPI_USER_ID);
- verifyPut(String.format(INVOICE_ID_PATH, id), jsonBody, headers, "", 400);
- }
-
@Test
void testUpdateInvoiceTransitionToPaidNoVoucherUpdate() {
logger.info("=== Test transition invoice to paid - voucher already paid ===");
@@ -2565,8 +2549,7 @@ void testUpdateInvoiceTransitionToPaidTransactionsAlreadyExists() {
verifyPut(String.format(INVOICE_ID_PATH, id), jsonBody, headers, "", 204);
assertThat(getRqRsEntries(HttpMethod.GET, FINANCE_TRANSACTIONS), hasSize(2));
- assertThat(getRqRsEntries(HttpMethod.GET, FUNDS), hasSize(2));
- assertThat(getRqRsEntries(HttpMethod.GET, CURRENT_FISCAL_YEAR), hasSize(1));
+ assertThat(getRqRsEntries(HttpMethod.GET, FUNDS), hasSize(1));
assertThat(getRqRsEntries(HttpMethod.POST, FINANCE_BATCH_TRANSACTIONS), hasSize(0));
}
@@ -3170,7 +3153,7 @@ private void checkCreditsPayments(Invoice invoice, List invoiceLine
assertThat(getRqRsEntries(HttpMethod.GET, FINANCE_TRANSACTIONS), hasSize(2));
- assertThat(getRqRsEntries(HttpMethod.GET, FUNDS), hasSize(2));
+ assertThat(getRqRsEntries(HttpMethod.GET, FUNDS), hasSize(1));
var transactions = getRqRsEntries(HttpMethod.POST, FINANCE_BATCH_TRANSACTIONS).stream()
.map(entries -> entries.mapTo(Batch.class))
diff --git a/src/test/java/org/folio/rest/impl/InvoicesProratedAdjustmentsTest.java b/src/test/java/org/folio/rest/impl/InvoicesProratedAdjustmentsTest.java
index 58a2a4bc9..534b81c3a 100644
--- a/src/test/java/org/folio/rest/impl/InvoicesProratedAdjustmentsTest.java
+++ b/src/test/java/org/folio/rest/impl/InvoicesProratedAdjustmentsTest.java
@@ -13,6 +13,7 @@
import static org.folio.rest.jaxrs.model.Adjustment.Prorate.BY_LINE;
import static org.folio.rest.jaxrs.model.Adjustment.Prorate.BY_QUANTITY;
import static org.folio.rest.jaxrs.model.Adjustment.Prorate.NOT_PRORATED;
+import static org.folio.rest.jaxrs.model.Adjustment.RelationToTotal.INCLUDED_IN;
import static org.folio.rest.jaxrs.model.Adjustment.Type.AMOUNT;
import static org.folio.rest.jaxrs.model.Adjustment.Type.PERCENTAGE;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -30,6 +31,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.UUID;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
@@ -1015,6 +1017,111 @@ public void testUpdateInvoiceWithThreeLinesAddingPercentageAdjustmentByLines() {
});
}
+ @Test
+ public void testUpdateInvoiceWithOneLinePercentageTypeByAmountProrateIncludedByTotalAdjustment() {
+ logger.info("=== Updating invoice with one line adding 7% adjustment by amount prorate included by total ===");
+
+ // Prepare data "from storage"
+ Invoice invoice = getMockAsJson(OPEN_INVOICE_SAMPLE_PATH).mapTo(Invoice.class).withId(randomUUID().toString());
+ invoice.getAdjustments().clear();
+ addMockEntry(INVOICES, invoice);
+
+ InvoiceLine invoiceLine = getMockInvoiceLine(invoice.getId()).withAdjustmentsTotal(0d).withSubTotal(30d).withInvoiceLineNumber("n-1");
+ addMockEntry(INVOICE_LINES, invoiceLine);
+
+ // Prepare request body
+ Invoice invoiceBody = copyObject(invoice);
+ Adjustment adjustment = new Adjustment()
+ .withId(UUID.randomUUID().toString())
+ .withDescription("VAT")
+ .withProrate(BY_AMOUNT)
+ .withType(PERCENTAGE)
+ .withRelationToTotal(INCLUDED_IN)
+ .withValue(7d);
+ invoiceBody.getAdjustments().add(adjustment);
+
+ // Send update request
+ verifyPut(String.format(INVOICE_ID_PATH, invoice.getId()), invoiceBody, "", 204);
+
+ // Verification
+ assertThat(getInvoiceUpdates(), hasSize(1));
+ assertThat(getInvoiceLineUpdates(), hasSize(1));
+
+ Invoice invoiceToStorage = getInvoiceUpdates().get(0).mapTo(Invoice.class);
+ assertThat(invoiceToStorage.getAdjustments(), hasSize(1));
+ assertThat(invoiceToStorage.getAdjustmentsTotal(), is(0.0));
+ Adjustment invoiceAdjustment = invoiceToStorage.getAdjustments().get(0);
+ assertThat(invoiceAdjustment.getId(), not(is(emptyOrNullString())));
+
+ Stream.of(invoiceLine.getId())
+ .forEach(id -> {
+ InvoiceLine lineToStorage = getLineToStorageById(id);
+ assertThat(lineToStorage.getAdjustments(), hasSize(1));
+ assertThat(lineToStorage.getAdjustmentsTotal(), is(0d));
+ assertThat(lineToStorage.getSubTotal(), is(28.04d));
+
+ Adjustment lineAdjustment = lineToStorage.getAdjustments().get(0);
+ verifyInvoiceLineAdjustmentCommon(invoiceAdjustment, lineAdjustment);
+ assertThat(lineAdjustment.getValue(), is(1.96d));
+ });
+ }
+
+ @Test
+ public void testUpdateInvoiceWithThreeLinesPercentageTypeByAmountProrateIncludedByTotalAdjustment() {
+ logger.info("=== Updating invoice with three lines adding 7% adjustment by amount prorate included by total ===");
+
+ // 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()).withAdjustmentsTotal(0d).withSubTotal(30d).withInvoiceLineNumber("n-1");
+ addMockEntry(INVOICE_LINES, invoiceLine1);
+
+ InvoiceLine invoiceLine2 = getMockInvoiceLine(invoice.getId()).withAdjustmentsTotal(0d).withSubTotal(30d).withInvoiceLineNumber("n-2");
+ addMockEntry(INVOICE_LINES, invoiceLine2);
+
+ InvoiceLine invoiceLine3 = getMockInvoiceLine(invoice.getId()).withAdjustmentsTotal(0d).withSubTotal(30d).withInvoiceLineNumber("n-3");
+ addMockEntry(INVOICE_LINES, invoiceLine3);
+
+ // Prepare request body
+ Invoice invoiceBody = copyObject(invoice);
+ Adjustment adjustment = new Adjustment()
+ .withId(UUID.randomUUID().toString())
+ .withDescription("VAT")
+ .withProrate(BY_AMOUNT)
+ .withType(PERCENTAGE)
+ .withRelationToTotal(INCLUDED_IN)
+ .withValue(7d);
+ invoiceBody.getAdjustments().add(adjustment);
+
+ // 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));
+ assertThat(invoiceToStorage.getAdjustmentsTotal(), is(0.0));
+ Adjustment invoiceAdjustment = invoiceToStorage.getAdjustments().get(0);
+ assertThat(invoiceAdjustment.getId(), not(is(emptyOrNullString())));
+
+ Stream.of(invoiceLine1.getId(), invoiceLine2.getId(), invoiceLine3.getId())
+ .forEach(id -> {
+ InvoiceLine lineToStorage = getLineToStorageById(id);
+ assertThat(lineToStorage.getAdjustments(), hasSize(1));
+ assertThat(lineToStorage.getAdjustmentsTotal(), is(0d));
+ assertThat(lineToStorage.getSubTotal(), is(28.04d));
+
+ Adjustment lineAdjustment = lineToStorage.getAdjustments().get(0);
+ verifyInvoiceLineAdjustmentCommon(invoiceAdjustment, lineAdjustment);
+ assertThat(lineAdjustment.getValue(), is(1.96d));
+ });
+ }
+
+
private InvoiceLine getLineToStorageById(String invoiceLineId) {
return getInvoiceLineUpdates().stream()
.filter(line -> invoiceLineId.equals(line.getString("id")))
diff --git a/src/test/java/org/folio/utils/UserPermissionsUtilTest.java b/src/test/java/org/folio/utils/UserPermissionsUtilTest.java
index b4253d8a8..e3a8afb8a 100644
--- a/src/test/java/org/folio/utils/UserPermissionsUtilTest.java
+++ b/src/test/java/org/folio/utils/UserPermissionsUtilTest.java
@@ -24,7 +24,7 @@ public class UserPermissionsUtilTest {
@DisplayName("Should not throw exception when approve permission is in position")
void shouldNotThrowExceptionWhenApprovePermissionIsInPosition() {
List permissionsList = List.of(
- "invoice.item.approve"
+ "invoice.item.approve.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -44,7 +44,7 @@ void shouldThrowExceptionWhenApprovePermissionIsAbsent() {
List permissionsList = Arrays.asList(
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
- "invoices.fiscal-year.update"
+ "invoices.fiscal-year.update.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -63,7 +63,7 @@ void shouldThrowExceptionWhenApprovePermissionIsAbsent() {
@DisplayName("Should not throw exception when pay permission is in position")
void shouldNotThrowExceptionWhenPayPermissionIsInPosition() {
List permissionsList = List.of(
- "invoice.item.pay"
+ "invoice.item.pay.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -83,7 +83,7 @@ void shouldThrowCorrectErrorCodeWhenPayPermissionIsAbsent() {
List permissionsList = Arrays.asList(
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
- "invoices.fiscal-year.update"
+ "invoices.fiscal-year.update.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -103,7 +103,7 @@ void shouldThrowCorrectErrorCodeWhenPayPermissionIsAbsent() {
@DisplayName("Should not throw exception when cancel permission is in position")
void shouldNotThrowExceptionWhenCancelPermissionIsInPosition() {
List permissionsList = List.of(
- "invoice.item.cancel"
+ "invoice.item.cancel.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -121,8 +121,8 @@ void shouldNotThrowExceptionWhenCancelPermissionIsInPosition() {
@DisplayName("Should throw correct error code when cancel permission absent")
void shouldThrowCorrectErrorCodeWhenCancelPermissionIsAbsent() {
List permissionsList = Arrays.asList(
- "invoice.item.approve",
- "invoice.item.pay"
+ "invoice.item.approve.execute",
+ "invoice.item.pay.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -144,7 +144,7 @@ void shouldThrowCorrectErrorCodeWhenAssignPermissionIsAbsent() {
List permissionsList = Arrays.asList(
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
- "invoices.fiscal-year.update"
+ "invoices.fiscal-year.update.execute"
);
String permissionsJsonArrayString = new JsonArray(permissionsList).encode();
@@ -170,7 +170,7 @@ void shouldNotThrowExceptionWhenAssignPermissionIsAssigned() {
List permissionsList = Arrays.asList(
"invoice.invoices.item.put",
"invoices.acquisitions-units-assignments.manage",
- "invoices.fiscal-year.update",
+ "invoices.fiscal-year.update.execute",
"invoices.acquisitions-units-assignments.assign"
);
@@ -260,7 +260,7 @@ void shouldThrowExceptionWhenFiscalYearUpdatePermissionIsAbsent() {
void shouldNotThrowExceptionWhenFiscalYearUpdatePermissionIsAssigned() {
// Create a list of permissions
List permissionsList = Arrays.asList(
- "invoices.fiscal-year.update",
+ "invoices.fiscal-year.update.execute",
"invoices.acquisitions-units-assignments.assign"
);