diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
index ebab6fa..b6edb8d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceFormatter.java
@@ -46,8 +46,11 @@ import org.joda.time.format.DateTimeFormatter;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.util.template.translation.TranslatorConfig;
import com.google.common.base.Objects;
@@ -80,13 +83,68 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
@Override
public List<InvoiceItem> getInvoiceItems() {
- final List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+ final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+
+ InvoiceItem mergedCBAItem = null;
+ InvoiceItem mergedInvoiceAdjustment = null;
for (final InvoiceItem item : invoice.getInvoiceItems()) {
+ if (InvoiceItemType.CBA_ADJ.equals(item.getInvoiceItemType())) {
+ // Merge CBA items to avoid confusing the customer, since these are internal
+ // adjustments (auto generated)
+ mergedCBAItem = mergeCBAItem(invoiceItems, mergedCBAItem, item);
+ } else if (InvoiceItemType.REFUND_ADJ.equals(item.getInvoiceItemType()) ||
+ InvoiceItemType.CREDIT_ADJ.equals(item.getInvoiceItemType())) {
+ // Merge refund adjustments and credit adjustments, as these are both
+ // the same for the customer (invoice adjustment)
+ mergedInvoiceAdjustment = mergeInvoiceAdjustmentItem(invoiceItems, mergedInvoiceAdjustment, item);
+ } else {
+ invoiceItems.add(item);
+ }
+ }
+ if (mergedCBAItem != null) {
+ invoiceItems.add(mergedCBAItem);
+ }
+ if (mergedInvoiceAdjustment != null) {
+ invoiceItems.add(mergedInvoiceAdjustment);
+ }
+
+ final List<InvoiceItem> formatters = new ArrayList<InvoiceItem>();
+ for (final InvoiceItem item : invoiceItems) {
formatters.add(new DefaultInvoiceItemFormatter(config, item, dateFormatter, locale));
}
return formatters;
}
+ private InvoiceItem mergeCBAItem(final List<InvoiceItem> invoiceItems, InvoiceItem mergedCBAItem, final InvoiceItem item) {
+ if (mergedCBAItem == null) {
+ mergedCBAItem = item;
+ } else {
+ // This is really just to be safe - they should always have the same currency
+ if (!mergedCBAItem.getCurrency().equals(item.getCurrency())) {
+ invoiceItems.add(item);
+ } else {
+ mergedCBAItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), invoice.getInvoiceDate(),
+ mergedCBAItem.getAmount().add(item.getAmount()), mergedCBAItem.getCurrency());
+ }
+ }
+ return mergedCBAItem;
+ }
+
+ private InvoiceItem mergeInvoiceAdjustmentItem(final List<InvoiceItem> invoiceItems, InvoiceItem mergedInvoiceAdjustment, final InvoiceItem item) {
+ if (mergedInvoiceAdjustment == null) {
+ mergedInvoiceAdjustment = item;
+ } else {
+ // This is really just to be safe - they should always have the same currency
+ if (!mergedInvoiceAdjustment.getCurrency().equals(item.getCurrency())) {
+ invoiceItems.add(item);
+ } else {
+ mergedInvoiceAdjustment = new CreditAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), invoice.getInvoiceDate(),
+ mergedInvoiceAdjustment.getAmount().add(item.getAmount()), mergedInvoiceAdjustment.getCurrency());
+ }
+ }
+ return mergedInvoiceAdjustment;
+ }
+
@Override
public boolean addInvoiceItem(final InvoiceItem item) {
return invoice.addInvoiceItem(item);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
index b2700eb..0f12045 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.template.formatters;
import java.math.BigDecimal;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
@@ -31,8 +32,15 @@ import org.testng.annotations.Test;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.InvoiceTestSuite;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.formatters.InvoiceFormatter;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
+import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
import com.ning.billing.util.email.templates.MustacheTemplateEngine;
import com.ning.billing.util.template.translation.TranslatorConfig;
@@ -48,6 +56,62 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuite {
}
@Test(groups = "fast")
+ public void testMergeItems() throws Exception {
+ // Scenario: single item with payment
+ // * $10 item
+ // Then, a repair occur:
+ // * $-10 repair
+ // * $10 generated CBA due to the repair (assume previous payment)
+ // Then, the invoice is adjusted for $1:
+ // * $-1 credit adjustment
+ // * $1 generated CBA due to the credit adjustment
+ // Then, we refund $1 with invoice level adjustment:
+ // * $-1 refund adjustment
+ final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+ UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+ new LocalDate(), BigDecimal.TEN, Currency.USD);
+ final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), fixedItem.getEndDate(),
+ fixedItem.getAmount().negate(), fixedItem.getCurrency(),
+ fixedItem.getId());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), fixedItem.getAmount(),
+ fixedItem.getCurrency());
+ final CreditAdjInvoiceItem creditAdjInvoiceItem = new CreditAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), BigDecimal.ONE.negate(), fixedItem.getCurrency());
+ final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), creditAdjInvoiceItem.getAmount().negate(),
+ fixedItem.getCurrency());
+ final RefundAdjInvoiceItem refundAdjInvoiceItem = new RefundAdjInvoiceItem(fixedItem.getInvoiceId(), fixedItem.getAccountId(),
+ fixedItem.getStartDate(), BigDecimal.ONE.negate(), fixedItem.getCurrency());
+ final Invoice invoice = new DefaultInvoice(fixedItem.getInvoiceId(), fixedItem.getAccountId(), null,
+ new LocalDate(), new LocalDate(), Currency.USD, false);
+ invoice.addInvoiceItem(fixedItem);
+ invoice.addInvoiceItem(repairAdjInvoiceItem);
+ invoice.addInvoiceItem(creditBalanceAdjInvoiceItem);
+ invoice.addInvoiceItem(creditAdjInvoiceItem);
+ invoice.addInvoiceItem(creditBalanceAdjInvoiceItem2);
+ invoice.addInvoiceItem(refundAdjInvoiceItem);
+ // Check the scenario
+ Assert.assertEquals(invoice.getBalance().doubleValue(), 9.00);
+ Assert.assertEquals(invoice.getCBAAmount().doubleValue(), 11.00);
+ Assert.assertEquals(invoice.getRefundAdjAmount().doubleValue(), -1.00);
+
+ // Verify the merge
+ final InvoiceFormatter formatter = new DefaultInvoiceFormatter(config, invoice, Locale.US);
+ final List<InvoiceItem> invoiceItems = formatter.getInvoiceItems();
+ Assert.assertEquals(invoiceItems.size(), 4);
+ Assert.assertEquals(invoiceItems.get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ Assert.assertEquals(invoiceItems.get(0).getAmount().doubleValue(), 10.00);
+ Assert.assertEquals(invoiceItems.get(1).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+ Assert.assertEquals(invoiceItems.get(1).getAmount().doubleValue(), -10.00);
+ Assert.assertEquals(invoiceItems.get(2).getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
+ Assert.assertEquals(invoiceItems.get(2).getAmount().doubleValue(), 11.00);
+ Assert.assertEquals(invoiceItems.get(3).getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+ Assert.assertEquals(invoiceItems.get(3).getAmount().doubleValue(), -2.00);
+ }
+
+ @Test(groups = "fast")
public void testFormattedAmount() throws Exception {
final FixedPriceInvoiceItem fixedItemEUR = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
UUID.randomUUID().toString(), UUID.randomUUID().toString(),