killbill-uncached

invoice: merge certain items on the HTML invoice * Merge generated

9/19/2012 7:41:34 PM

Details

diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index c1e3140..4b566af 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -57,7 +57,6 @@ import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
 import com.ning.billing.junction.api.BillingEventSet;
 import com.ning.billing.util.clock.Clock;
 
-import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
index 880dde2..3b43340 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditAdjInvoiceItem.java
@@ -43,12 +43,6 @@ public class CreditAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        final String secondDescription;
-        if (getAmount().compareTo(BigDecimal.ZERO) >= 0) {
-            secondDescription = "account credit";
-        } else {
-            secondDescription = "use of account credit";
-        }
-        return String.format("Adjustment (%s)", secondDescription);
+        return "Invoice adjustment";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
index 9cc8b0b..6ccef1e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RefundAdjInvoiceItem.java
@@ -43,6 +43,6 @@ public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "Adjustment (refund)";
+        return "Invoice adjustment";
     }
 }
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(),