killbill-memoizeit

invoice: presentation tweaks * Fix default invoice items

9/19/2012 5:20:53 PM

Details

diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
index 9ef4144..7a063cc 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceFormatter.java
@@ -19,5 +19,12 @@ package com.ning.billing.invoice.api.formatters;
 import com.ning.billing.invoice.api.Invoice;
 
 public interface InvoiceFormatter extends Invoice {
+
     public String getFormattedInvoiceDate();
+
+    public String getFormattedChargedAmount();
+
+    public String getFormattedPaidAmount();
+
+    public String getFormattedBalance();
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
index 7175fde..2927744 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/formatters/InvoiceItemFormatter.java
@@ -19,7 +19,10 @@ package com.ning.billing.invoice.api.formatters;
 import com.ning.billing.invoice.api.InvoiceItem;
 
 public interface InvoiceItemFormatter extends InvoiceItem {
+
     public String getFormattedStartDate();
 
     public String getFormattedEndDate();
+
+    public String getFormattedAmount();
 }
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 6c856e8..880dde2 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
@@ -19,13 +19,13 @@ package com.ning.billing.invoice.model;
 import java.math.BigDecimal;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class CreditAdjInvoiceItem extends AdjInvoiceItem {
+
     public CreditAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
                                 final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, date, date, amount, currency);
@@ -43,6 +43,12 @@ public class CreditAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "credit-adj";
+        final String secondDescription;
+        if (getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+            secondDescription = "account credit";
+        } else {
+            secondDescription = "use of account credit";
+        }
+        return String.format("Adjustment (%s)", secondDescription);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
index 3eb05b2..2c4c72e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
+
     public CreditBalanceAdjInvoiceItem(final UUID invoiceId, final UUID accountId,
                                        final LocalDate date, final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, date, date, amount, currency);
@@ -42,6 +43,12 @@ public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "cba-adj";
+        final String secondDescription;
+        if (getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+            secondDescription = "account credit";
+        } else {
+            secondDescription = "use of account credit";
+        }
+        return String.format("Adjustment (%s)", secondDescription);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
index c8f0245..44c915e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -41,7 +41,11 @@ public class ExternalChargeInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s (external charge) on %s", getPlanName(), getStartDate().toString());
+        if (getPlanName() == null) {
+            return "External charge";
+        } else {
+            return String.format("%s (external charge)", getPlanName());
+        }
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
index f2d641e..d169d78 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -28,6 +28,7 @@ import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class FixedPriceInvoiceItem extends InvoiceItemBase {
+
     public FixedPriceInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId, final String planName, final String phaseName,
                                  final LocalDate date, final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, date, null, amount, currency);
@@ -40,7 +41,15 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s (fixed price) on %s", getPhaseName(), getStartDate().toString());
+        if (getPhaseName() == null) {
+            return "Fixed price charge";
+        } else {
+            if (getAmount().compareTo(BigDecimal.ZERO) == 0) {
+                return getPhaseName();
+            } else {
+                return String.format("%s (fixed price)", getPhaseName());
+            }
+        }
     }
 
     @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
index cbfe229..5392adf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
@@ -45,6 +45,6 @@ public class ItemAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "item-adj";
+        return "Invoice item adjustment";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
index d6f98a2..c3f4afa 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -50,7 +50,7 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s from %s to %s", phaseName, startDate.toString(dateTimeFormatter), endDate.toString(dateTimeFormatter));
+        return phaseName;
     }
 
     @Override
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 9091b6c..9cc8b0b 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
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class RefundAdjInvoiceItem extends AdjInvoiceItem {
+
     public RefundAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
                                 final BigDecimal amount, final Currency currency) {
         super(invoiceId, accountId, date, date, amount, currency);
@@ -42,6 +43,6 @@ public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "refund-adj";
+        return "Adjustment (refund)";
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
index 36327d2..1b3155b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RepairAdjInvoiceItem.java
@@ -25,6 +25,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItemType;
 
 public class RepairAdjInvoiceItem extends AdjInvoiceItem {
+
     public RepairAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final Currency currency, final UUID reversingId) {
         super(invoiceId, accountId, startDate, endDate, amount, currency, reversingId);
@@ -42,6 +43,6 @@ public class RepairAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "repair-adj";
+        return "Adjustment (entitlement change)";
     }
 }
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 f9ef212..ebab6fa 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
@@ -33,6 +33,7 @@
 package com.ning.billing.invoice.template.formatters;
 
 import java.math.BigDecimal;
+import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -56,7 +57,7 @@ import com.google.common.collect.ImmutableList;
 import static com.ning.billing.invoice.template.formatters.DefaultAmountFormatter.round;
 
 /**
- * Format invoice fields. Note that the Mustache engine won't accept null values.
+ * Format invoice fields
  */
 public class DefaultInvoiceFormatter implements InvoiceFormatter {
 
@@ -147,6 +148,24 @@ public class DefaultInvoiceFormatter implements InvoiceFormatter {
     }
 
     @Override
+    public String getFormattedChargedAmount() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        return number.format(getChargedAmount().doubleValue());
+    }
+
+    @Override
+    public String getFormattedPaidAmount() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        return number.format(getPaidAmount().doubleValue());
+    }
+
+    @Override
+    public String getFormattedBalance() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        return number.format(getBalance().doubleValue());
+    }
+
+    @Override
     public boolean isMigrationInvoice() {
         return invoice.isMigrationInvoice();
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index 04982f4..b16a17e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -17,15 +17,13 @@
 package com.ning.billing.invoice.template.formatters;
 
 import java.math.BigDecimal;
+import java.text.NumberFormat;
 import java.util.Locale;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormatter;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Strings;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
@@ -34,12 +32,16 @@ import com.ning.billing.util.template.translation.DefaultCatalogTranslator;
 import com.ning.billing.util.template.translation.Translator;
 import com.ning.billing.util.template.translation.TranslatorConfig;
 
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+
 import static com.ning.billing.invoice.template.formatters.DefaultAmountFormatter.round;
 
 /**
- * Format invoice item fields. Note that the Mustache engine won't accept null values.
+ * Format invoice item fields
  */
 public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
+
     private final Translator translator;
 
     private final InvoiceItem item;
@@ -65,6 +67,13 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
     }
 
     @Override
+    public String getFormattedAmount() {
+        final NumberFormat number = NumberFormat.getCurrencyInstance(locale);
+        number.setCurrency(java.util.Currency.getInstance(item.getCurrency().toString()));
+        return number.format(getAmount().doubleValue());
+    }
+
+    @Override
     public InvoiceItemType getInvoiceItemType() {
         return item.getInvoiceItemType();
     }
@@ -86,12 +95,12 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
 
     @Override
     public String getFormattedStartDate() {
-        return Strings.nullToEmpty(item.getStartDate().toString(dateFormatter));
+        return item.getStartDate().toString(dateFormatter);
     }
 
     @Override
     public String getFormattedEndDate() {
-        return Strings.nullToEmpty(item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter));
+        return item.getEndDate() == null ? null : item.getEndDate().toString(dateFormatter);
     }
 
     @Override
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
new file mode 100644
index 0000000..b2700eb
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.formatters;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+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.model.DefaultInvoice;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+public class TestDefaultInvoiceFormatter extends InvoiceTestSuite {
+
+    private TranslatorConfig config;
+    private MustacheTemplateEngine templateEngine;
+
+    @BeforeSuite(groups = "fast")
+    public void setup() {
+        config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        templateEngine = new MustacheTemplateEngine();
+    }
+
+    @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(),
+                                                                             new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
+        final Invoice invoiceEUR = new DefaultInvoice(UUID.randomUUID(), new LocalDate(), new LocalDate(), Currency.EUR);
+        invoiceEUR.addInvoiceItem(fixedItemEUR);
+
+        checkOutput(invoiceEUR,
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>{{invoice.formattedChargedAmount}}</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>{{invoice.formattedPaidAmount}}</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>{{invoice.formattedBalance}}</strong></td>\n" +
+                    "</tr>",
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>1 499,95 €</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>0,00 €</strong></td>\n" +
+                    "</tr>\n" +
+                    "<tr>\n" +
+                    "    <td class=\"amount\"><strong>1 499,95 €</strong></td>\n" +
+                    "</tr>",
+                    Locale.FRANCE);
+    }
+
+    private void checkOutput(final Invoice invoice, final String template, final String expected, final Locale locale) {
+        final Map<String, Object> data = new HashMap<String, Object>();
+        data.put("invoice", new DefaultInvoiceFormatter(config, invoice, locale));
+
+        final String formattedText = templateEngine.executeTemplateText(template, data);
+        Assert.assertEquals(formattedText, expected);
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
new file mode 100644
index 0000000..e052935
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.template.formatters;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.skife.config.ConfigurationObjectFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.InvoiceTestSuite;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.email.templates.MustacheTemplateEngine;
+import com.ning.billing.util.template.translation.TranslatorConfig;
+
+public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuite {
+
+    private TranslatorConfig config;
+    private MustacheTemplateEngine templateEngine;
+
+    @BeforeSuite(groups = "fast")
+    public void setup() {
+        config = new ConfigurationObjectFactory(System.getProperties()).build(TranslatorConfig.class);
+        templateEngine = new MustacheTemplateEngine();
+    }
+
+    @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(),
+                                                                             new LocalDate(), new BigDecimal("1499.95"), Currency.EUR);
+        checkOutput(fixedItemEUR, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+                    "<td class=\"amount\">1 499,95 €</td>", Locale.FRANCE);
+
+        final FixedPriceInvoiceItem fixedItemUSD = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("-1114.751625346"), Currency.USD);
+        checkOutput(fixedItemUSD, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}", "<td class=\"amount\">($1,114.75)</td>");
+
+        // Check locale/currency mismatch (locale is set at the account level)
+        final FixedPriceInvoiceItem fixedItemGBP = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                             UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                             new LocalDate(), new BigDecimal("8.07"), Currency.GBP);
+        checkOutput(fixedItemGBP, "{{#invoiceItem}}<td class=\"amount\">{{formattedAmount}}</td>{{/invoiceItem}}",
+                    "<td class=\"amount\">8,07 GBP</td>", Locale.FRANCE);
+    }
+
+    @Test(groups = "fast")
+    public void testNullEndDate() throws Exception {
+        final LocalDate startDate = new LocalDate(2012, 12, 1);
+        final FixedPriceInvoiceItem fixedItem = new FixedPriceInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                          UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                          startDate, BigDecimal.TEN, Currency.USD);
+        checkOutput(fixedItem,
+                    "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
+                    "<td>Dec 1, 2012</td>");
+    }
+
+    @Test(groups = "fast")
+    public void testNonNullEndDate() throws Exception {
+        final LocalDate startDate = new LocalDate(2012, 12, 1);
+        final LocalDate endDate = new LocalDate(2012, 12, 31);
+        final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), null, null,
+                                                                            UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                                            startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, Currency.USD);
+        checkOutput(recurringItem,
+                    "{{#invoiceItem}}<td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td>{{/invoiceItem}}",
+                    "<td>Dec 1, 2012 - Dec 31, 2012</td>");
+    }
+
+    private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected) {
+        checkOutput(invoiceItem, template, expected, Locale.US);
+    }
+
+    private void checkOutput(final InvoiceItem invoiceItem, final String template, final String expected, final Locale locale) {
+        final Map<String, Object> data = new HashMap<String, Object>();
+        data.put("invoiceItem", new DefaultInvoiceItemFormatter(config, invoiceItem, DateTimeFormat.mediumDate(), locale));
+
+        final String formattedText = templateEngine.executeTemplateText(template, data);
+        Assert.assertEquals(formattedText, expected);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
index f6a76a3..8ddc2aa 100644
--- a/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
+++ b/util/src/main/java/com/ning/billing/util/email/templates/MustacheTemplateEngine.java
@@ -23,13 +23,21 @@ import java.util.Map;
 
 import com.ning.billing.util.config.UriAccessor;
 import com.ning.billing.util.io.IOUtils;
+
+import com.google.common.annotations.VisibleForTesting;
 import com.samskivert.mustache.Mustache;
 import com.samskivert.mustache.Template;
 
 public class MustacheTemplateEngine implements TemplateEngine {
+
     @Override
     public String executeTemplate(final String templateName, final Map<String, Object> data) throws IOException {
         final String templateText = getTemplateText(templateName);
+        return executeTemplateText(templateText, data);
+    }
+
+    @VisibleForTesting
+    public String executeTemplateText(final String templateText, final Map<String, Object> data) {
         final Template template = Mustache.compiler().compile(templateText);
         return template.execute(data);
     }