Skip to content

Commit

Permalink
[MODINVOICE-554-revert]. Revert 2 PR changes (#514)
Browse files Browse the repository at this point in the history
* Revert "[MODINVOICE-554-2]. Change invoice total and adjustments total calculation (#512)"

This reverts commit 8dcd006.

* Revert "[MODINVOICE-554]. Invoices app: Incorrect formula for calculating adjustments, that are included and pro-rated by amount (#509)"

This reverts commit 4ababdf.
  • Loading branch information
BKadirkhodjaev authored Oct 22, 2024
1 parent 8dcd006 commit 5cc0b48
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 246 deletions.
30 changes: 19 additions & 11 deletions src/main/java/org/folio/invoices/utils/HelperUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -34,13 +33,13 @@

import io.vertx.core.Vertx;
import io.vertxconcurrent.Semaphore;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.folio.invoices.rest.exceptions.HttpException;
import org.folio.okapi.common.GenericCompositeFuture;
import org.folio.rest.acq.model.finance.ExchangeRate;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.impl.ProtectionHelper;
import org.folio.rest.jaxrs.model.Adjustment;
Expand All @@ -59,7 +58,6 @@
import io.vertx.core.json.JsonObject;
import one.util.streamex.StreamEx;

@Log4j2
public class HelperUtils {

public static final String INVOICE_ID = "invoiceId";
Expand All @@ -71,10 +69,10 @@ public class HelperUtils {
public static final String BATCH_VOUCHER_EXPORT = "batchVoucherExport";

private static final Pattern CQL_SORT_BY_PATTERN = Pattern.compile("(.*)(\\ssortBy\\s.*)", Pattern.CASE_INSENSITIVE);
private static final EnumSet<Adjustment.RelationToTotal> SUPPORTED_RELATION_TO_TOTALS =
EnumSet.of(Adjustment.RelationToTotal.IN_ADDITION_TO, Adjustment.RelationToTotal.INCLUDED_IN);


private HelperUtils() {

}

/**
Expand Down Expand Up @@ -110,7 +108,7 @@ public static String combineCqlExpressions(String term, String... expressions) {

public static MonetaryAmount calculateAdjustmentsTotal(List<Adjustment> adjustments, MonetaryAmount subTotal) {
return adjustments.stream()
.filter(adj -> SUPPORTED_RELATION_TO_TOTALS.contains(adj.getRelationToTotal()))
.filter(adj -> adj.getRelationToTotal().equals(Adjustment.RelationToTotal.IN_ADDITION_TO))
.map(adj -> calculateAdjustment(adj, subTotal))
.collect(MonetaryFunctions.summarizingMonetary(subTotal.getCurrency()))
.getSum()
Expand Down Expand Up @@ -202,6 +200,7 @@ public interface FunctionReturningFuture<I, O> {
}

public static double calculateVoucherAmount(Voucher voucher, List<VoucherLine> voucherLines) {

CurrencyUnit currency = Monetary.getCurrency(voucher.getSystemCurrency());

MonetaryAmount amount = voucherLines.stream()
Expand All @@ -225,6 +224,7 @@ public static void calculateInvoiceLineTotals(InvoiceLine invoiceLine, Invoice i
CurrencyUnit currencyUnit = Monetary.getCurrency(currency);
BigDecimal invoiceLineSubTotal = BigDecimal.valueOf(invoiceLine.getSubTotal()).setScale(2, RoundingMode.HALF_EVEN);
MonetaryAmount subTotal = Money.of(invoiceLineSubTotal, currencyUnit);

MonetaryAmount adjustmentTotals = calculateAdjustmentsTotal(invoiceLine.getAdjustments(), subTotal);
MonetaryAmount total = adjustmentTotals.add(subTotal);
invoiceLine.setAdjustmentsTotal(convertToDoubleWithRounding(adjustmentTotals));
Expand All @@ -244,11 +244,11 @@ public static String getNoAcqUnitCQL(String entity) {
}

public static String getAcqUnitIdsQueryParamName(String entity) {
return switch (entity) {
case INVOICE_LINES -> INVOICES + "." + ProtectionHelper.ACQUISITIONS_UNIT_IDS;
case VOUCHER_LINES -> VOUCHERS_STORAGE + "." + ProtectionHelper.ACQUISITIONS_UNIT_IDS;
default -> ProtectionHelper.ACQUISITIONS_UNIT_IDS;
};
switch (entity) {
case INVOICE_LINES: return INVOICES + "." + ProtectionHelper.ACQUISITIONS_UNIT_IDS;
case VOUCHER_LINES: return VOUCHERS_STORAGE + "." + ProtectionHelper.ACQUISITIONS_UNIT_IDS;
default: return ProtectionHelper.ACQUISITIONS_UNIT_IDS;
}
}

public static String getId(JsonObject jsonObject) {
Expand Down Expand Up @@ -305,6 +305,14 @@ public static ConversionQuery buildConversionQuery(Invoice invoice, String syste
return ConversionQueryBuilder.of().setBaseCurrency(invoice.getCurrency()).setTermCurrency(systemCurrency).build();
}

public static ConversionQuery buildConversionQuery(ExchangeRate exchangeRate) {

return ConversionQueryBuilder.of().setBaseCurrency(exchangeRate.getFrom())
.setTermCurrency(exchangeRate.getTo())
.set(RATE_KEY, exchangeRate.getExchangeRate()).build();

}

public static boolean isNotFound(Throwable t) {
return t instanceof HttpException && ((HttpException) t).getCode() == 404;
}
Expand Down
54 changes: 10 additions & 44 deletions src/main/java/org/folio/services/adjusment/AdjustmentsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import io.vertx.core.json.JsonObject;

public class AdjustmentsService {

private final Logger logger = LogManager.getLogger(this.getClass());
public static final Predicate<Adjustment> NOT_PRORATED_ADJUSTMENTS_PREDICATE = adj -> adj.getProrate() == NOT_PRORATED;
public static final Predicate<Adjustment> PRORATED_ADJUSTMENTS_PREDICATE = NOT_PRORATED_ADJUSTMENTS_PREDICATE.negate();
Expand Down Expand Up @@ -66,7 +65,7 @@ public List<InvoiceLine> applyProratedAdjustments(List<InvoiceLine> lines, Invoi
updatedLines.addAll(applyProratedAdjustmentByLines(adjustment, lines, currencyUnit));
break;
case BY_AMOUNT:
updatedLines.addAll(applyAdjustmentsAndUpdateLines(adjustment, lines, currencyUnit));
updatedLines.addAll(applyProratedAdjustmentByAmount(adjustment, lines, currencyUnit));
break;
case BY_QUANTITY:
updatedLines.addAll(applyProratedAdjustmentByQuantity(adjustment, lines, currencyUnit));
Expand All @@ -81,15 +80,6 @@ public List<InvoiceLine> applyProratedAdjustments(List<InvoiceLine> lines, Invoi
.collect(toList());
}

private List<InvoiceLine> applyAdjustmentsAndUpdateLines(Adjustment adjustment, List<InvoiceLine> 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<InvoiceLine> lines, Invoice invoice) {
List<Adjustment> proratedAdjustments = getProratedAdjustments(invoice);

Expand All @@ -98,6 +88,7 @@ public void processProratedAdjustments(List<InvoiceLine> lines, Invoice invoice)

// Apply prorated adjustments to each invoice line
applyProratedAdjustments(lines, invoice);

}

/**
Expand All @@ -108,10 +99,10 @@ public void processProratedAdjustments(List<InvoiceLine> lines, Invoice invoice)
void filterDeletedAdjustments(List<Adjustment> proratedAdjustments, List<InvoiceLine> invoiceLines) {
List<String> adjIds = proratedAdjustments.stream()
.map(Adjustment::getId)
.toList();
.collect(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())));
}

/**
Expand Down Expand Up @@ -188,7 +179,7 @@ private List<InvoiceLine> applyAmountTypeProratedAdjustments(Adjustment adjustme
int remainderSignum = remainder.signum();
MonetaryAmount smallestUnit = getSmallestUnit(expectedAdjustmentTotal, remainderSignum);

for (ListIterator<InvoiceLine> iterator = getIterator(lines, remainderSignum); isIteratorHasNext(iterator, remainderSignum); ) {
for (ListIterator<InvoiceLine> iterator = getIterator(lines, remainderSignum); isIteratorHasNext(iterator, remainderSignum);) {

final InvoiceLine line = iteratorNext(iterator, remainderSignum);
MonetaryAmount amount = lineIdAdjustmentValueMap.get(line.getId());
Expand All @@ -199,7 +190,8 @@ private List<InvoiceLine> applyAmountTypeProratedAdjustments(Adjustment adjustme
}

Adjustment proratedAdjustment = prepareAdjustmentForLine(adjustment);
proratedAdjustment.setValue(amount.getNumber().doubleValue());
proratedAdjustment.setValue(amount.getNumber()
.doubleValue());
if (addAdjustmentToLine(line, proratedAdjustment)) {
updatedLines.add(line);
}
Expand All @@ -208,41 +200,13 @@ private List<InvoiceLine> applyAmountTypeProratedAdjustments(Adjustment adjustme
return updatedLines;
}

private List<InvoiceLine> applyProratedAmountTypeIncludedInAdjustments(Adjustment adjustment, List<InvoiceLine> lines,
CurrencyUnit currencyUnit) {
List<InvoiceLine> 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());
line.withAdjustmentsTotal(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<InvoiceLine> applyProratedAdjustmentByAmount(Adjustment adjustment, List<InvoiceLine> lines,
CurrencyUnit currencyUnit) {

if (adjustment.getType() == Adjustment.Type.PERCENTAGE) {
adjustment = convertToAmountAdjustment(adjustment, lines, currencyUnit);
}
Expand All @@ -268,6 +232,7 @@ private BiFunction<MonetaryAmount, InvoiceLine, MonetaryAmount> prorateByAmountF
*/
private List<InvoiceLine> applyProratedAdjustmentByQuantity(Adjustment adjustment, List<InvoiceLine> lines,
CurrencyUnit currencyUnit) {

if (adjustment.getType() == Adjustment.Type.PERCENTAGE) {
return applyPercentageAdjustmentsByQuantity(adjustment, lines, currencyUnit);
}
Expand Down Expand Up @@ -320,4 +285,5 @@ private InvoiceLine iteratorNext(ListIterator<InvoiceLine> iterator, int remaind
private BiFunction<MonetaryAmount, InvoiceLine, MonetaryAmount> prorateByLines(List<InvoiceLine> lines) {
return (amount, line) -> amount.divide(lines.size()).with(Monetary.getDefaultRounding());
}

}
6 changes: 3 additions & 3 deletions src/test/java/org/folio/rest/impl/InvoiceLinesApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ public void testPostInvoicingInvoiceLinesWithRelationshipTotal() {


InvoiceLine reqData = getMockAsJson(INVOICE_LINE_ADJUSTMENTS_SAMPLE_PATH).mapTo(InvoiceLine.class);
// set adjustment relation to Included In
// set adjustment realtion to Included In
reqData.getAdjustments()
.get(0)
.setRelationToTotal(Adjustment.RelationToTotal.INCLUDED_IN);
Expand All @@ -638,8 +638,8 @@ public void testPostInvoicingInvoiceLinesWithRelationshipTotal() {
InvoiceLine invoiceLine = verifyPostResponse(INVOICE_LINES_PATH, jsonBody, prepareHeaders(X_OKAPI_TENANT), APPLICATION_JSON,
201).as(InvoiceLine.class);

double expectedAdjustmentsTotal = 7.02d;
double expectedTotal = 27.04d;
double expectedAdjustmentsTotal = 5d;
double expectedTotal = 25.02d;

assertThat(invoiceLine.getAdjustmentsTotal(), equalTo(expectedAdjustmentsTotal));
assertThat(invoiceLine.getTotal(), equalTo(expectedTotal));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
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;
Expand All @@ -22,7 +19,6 @@

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;
Expand All @@ -31,7 +27,6 @@
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;
Expand Down Expand Up @@ -187,82 +182,6 @@ 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(1.96d));
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(1.96d));
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")))
Expand Down
Loading

0 comments on commit 5cc0b48

Please sign in to comment.