killbill-aplcache

Merge branch 'inv-ent-integration' of git@github.com:ning/killbill

2/1/2012 7:31:06 PM

Details

diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index a9070fd..8cadd9a 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -28,12 +28,14 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
 
     UUID getSubscriptionId();
 
+    String getPlanName();
+
+    String getPhaseName();
+
     DateTime getStartDate();
 
     DateTime getEndDate();
 
-    String getDescription();
-
     BigDecimal getRecurringAmount();
 
     BigDecimal getRecurringRate();
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
index 2df4f1f..06e16e6 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -171,7 +171,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
 		return this;
 	}
 
-	protected DefaultPlanPhase setReccuringPrice(DefaultInternationalPrice price) {
+	protected DefaultPlanPhase setRecurringPrice(DefaultInternationalPrice price) {
 		this.recurringPrice = price;
 		return this;
 	}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
index cb6a887..5fc51b8 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -64,7 +64,7 @@ public class MockCatalog extends StandaloneCatalog {
 		DefaultProduct[] products = getCurrentProducts();
 		DefaultPlan[] plans = new DefaultPlan[products.length];
 		for(int i = 0; i < products.length; i++) {
-			DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setReccuringPrice(new DefaultInternationalPrice());
+			DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice());
 			plans[i] = new MockPlan().setName(products[i].getName().toLowerCase() + "-plan").setProduct(products[i]).setFinalPhase(phase);
 		}
 		setPlans(plans);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
index 2ef5768..d4ae5ff 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
@@ -18,6 +18,7 @@ package com.ning.billing.catalog;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.TimeUnit;
 
 import javax.annotation.Nullable;
@@ -33,7 +34,7 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(billingPeriod);
 		setPhaseType(type);
 		setDuration(duration);
-		setReccuringPrice(recurringPrice);
+		setRecurringPrice(recurringPrice);
 		setFixedPrice(fixedPrice);
 	}
     
@@ -59,7 +60,7 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(billingPeriod);
 		setPhaseType(phaseType);
 		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
-		setReccuringPrice(recurringPrice);
+		setRecurringPrice(recurringPrice);
 		setFixedPrice(fixedPrice);
 		setPlan(new MockPlan(this));
 	}
@@ -68,10 +69,17 @@ public class MockPlanPhase extends DefaultPlanPhase {
 		setBillingPeriod(BillingPeriod.MONTHLY);
 		setPhaseType(PhaseType.EVERGREEN);
 		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
-		setReccuringPrice(new MockInternationalPrice());
+		setRecurringPrice(new MockInternationalPrice());
 		setFixedPrice(null);
 		setPlan(mockPlan);
 	}
 
-	
+    public MockPlanPhase(Plan plan, PhaseType phaseType) {
+		setBillingPeriod(BillingPeriod.MONTHLY);
+		setPhaseType(phaseType);
+		setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
+		setRecurringPrice(new MockInternationalPrice());
+		setFixedPrice(null);
+		setPlan(plan);
+	}
 }
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
index d778c4b..9b07bfe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -32,17 +32,17 @@ public class TestPlanPhase {
 	public void testValidation() {
 		log.info("Testing Plan Phase Validation");
 		
-		DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setReccuringPrice(null).setFixedPrice(new DefaultInternationalPrice());
+		DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setRecurringPrice(null).setFixedPrice(new DefaultInternationalPrice());
 		ValidationErrors errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
 
-		pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setReccuringPrice(new MockInternationalPrice());
+		pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setRecurringPrice(new MockInternationalPrice());
 		errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
 
-		pp = new MockPlanPhase().setReccuringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
+		pp = new MockPlanPhase().setRecurringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
 		errors = pp.validate(new MockCatalog(), new ValidationErrors());
 		errors.log(log);
 		Assert.assertEquals(errors.size(), 1);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 76e0dbf..84a21ef 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -79,9 +79,10 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
                         q.bind("id", item.getId().toString());
                         q.bind("invoiceId", item.getInvoiceId().toString());
                         q.bind("subscriptionId", item.getSubscriptionId().toString());
+                        q.bind("planName", item.getPlanName());
+                        q.bind("phaseName", item.getPhaseName());
                         q.bind("startDate", item.getStartDate().toDate());
                         q.bind("endDate", item.getEndDate() == null ? null : item.getEndDate().toDate());
-                        q.bind("description", item.getDescription());
                         q.bind("recurringAmount", item.getRecurringAmount() == null ? BigDecimal.ZERO : item.getRecurringAmount());
                         q.bind("recurringRate", item.getRecurringRate() == null ? BigDecimal.ZERO : item.getRecurringRate());
                         q.bind("fixedAmount", item.getFixedAmount() == null ? BigDecimal.ZERO : item.getFixedAmount());
@@ -98,16 +99,17 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
             UUID id = UUID.fromString(result.getString("id"));
             UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
             UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+            String planName = result.getString("plan_name");
+            String phaseName = result.getString("phase_name");
             DateTime startDate = new DateTime(result.getTimestamp("start_date"));
             DateTime endDate = new DateTime(result.getTimestamp("end_date"));
-            String description = result.getString("description");
             BigDecimal recurringAmount = result.getBigDecimal("recurring_amount");
             BigDecimal recurringRate = result.getBigDecimal("recurring_rate");
             BigDecimal fixedAmount = result.getBigDecimal("fixed_amount");
             Currency currency = Currency.valueOf(result.getString("currency"));
 
-            return new DefaultInvoiceItem(id, invoiceId, subscriptionId, startDate, endDate,
-                                          description, recurringAmount, recurringRate, fixedAmount, currency);
+            return new DefaultInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+                                          recurringAmount, recurringRate, fixedAmount, currency);
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index e12adc3..948638c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.invoice;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.UUID;
@@ -111,20 +112,36 @@ public class InvoiceListener {
 
         if (invoice == null) {
             log.info("Generated null invoice.");
+            outputDebugData(events, invoiceItemList);
+        } else {
+            log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+
             if (VERBOSE_OUTPUT) {
-                for (BillingEvent event : events) {
-                    log.info(event.toString());
-                }
-                for (InvoiceItem item : invoiceItemList) {
+                log.info("New items");
+                for (InvoiceItem item : invoice.getInvoiceItems()) {
                     log.info(item.toString());
                 }
             }
-        } else {
-            log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+
+            outputDebugData(events, invoiceItemList);
 
             if (invoice.getNumberOfItems() > 0) {
                 invoiceDao.create(invoice);
             }
         }
     }
+
+    private void outputDebugData(Collection<BillingEvent> events, Collection<InvoiceItem> invoiceItemList) {
+        if (VERBOSE_OUTPUT) {
+            log.info("Events");
+            for (BillingEvent event : events) {
+                log.info(event.toString());
+            }
+
+            log.info("Existing items");
+            for (InvoiceItem item : invoiceItemList) {
+                log.info(item.toString());
+            }
+        }
+    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 30a6957..9bbc0c7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -16,11 +16,9 @@
 
 package com.ning.billing.invoice.model;
 
-
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
+import java.util.Iterator;
 import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.format.ISODateTimeFormat;
@@ -29,7 +27,6 @@ import org.slf4j.LoggerFactory;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.InternationalPrice;
 import com.ning.billing.entitlement.api.billing.BillingEvent;
 import com.ning.billing.entitlement.api.billing.BillingModeType;
 import com.ning.billing.invoice.api.Invoice;
@@ -80,18 +77,18 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         Collections.sort(currentItems);
         Collections.sort(existingItems);
 
-        List<InvoiceItem> existingItemsToRemove = new ArrayList<InvoiceItem>();
-
         for (final InvoiceItem currentItem : currentItems) {
+            Iterator<InvoiceItem> it = existingItems.iterator();
+
             // see if there are any existing items that are covered by the current item
-            for (final InvoiceItem existingItem : existingItems) {
+            while (it.hasNext()) {
+                InvoiceItem existingItem = it.next();
                 if (currentItem.duplicates(existingItem)) {
                     currentItem.subtract(existingItem);
-                    existingItemsToRemove.add(existingItem);
+                    it.remove();
                 }
             }
         }
-        existingItems.removeAll(existingItemsToRemove);
 
         // remove cancelling pairs of invoice items
         existingItems.removeCancellingPairs();
@@ -134,14 +131,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         return items;
     }
 
-    private void processEvent(final UUID invoiceId, final BillingEvent event, final List<InvoiceItem> items,
+    private void processEvent(final UUID invoiceId, final BillingEvent event, final InvoiceItemList items,
                               final DateTime targetDate, final Currency targetCurrency) {
     	try {
             BigDecimal recurringRate = event.getRecurringPrice() == null ? null : event.getRecurringPrice().getPrice(targetCurrency);
             BigDecimal fixedPrice = event.getFixedPrice() == null ? null : event.getFixedPrice().getPrice(targetCurrency);
 
     		BigDecimal numberOfBillingPeriods;
-            BigDecimal recurringAmount =null;
+            BigDecimal recurringAmount = null;
 
             if (recurringRate != null) {
                 numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
@@ -150,8 +147,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
             BillingMode billingMode = getBillingMode(event.getBillingMode());
             DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
-            if (!billThroughDate.isAfter(targetDate.plusMonths(event.getBillingPeriod().getNumberOfMonths()))) {
-                addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, fixedPrice, targetCurrency);
+            if ((event.getBillingPeriod() == BillingPeriod.NO_BILLING_PERIOD) || (!billThroughDate.isAfter(targetDate.plusMonths(event.getBillingPeriod().getNumberOfMonths())))) {
+                BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
+                addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
             }
     	} catch (CatalogApiException e) {
             log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
@@ -161,13 +159,13 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     }
 
     private void processEvents(final UUID invoiceId, final BillingEvent firstEvent, final BillingEvent secondEvent,
-                               final List<InvoiceItem> items, final DateTime targetDate, final Currency targetCurrency) {
+                               final InvoiceItemList items, final DateTime targetDate, final Currency targetCurrency) {
     	try {
             BigDecimal recurringRate = firstEvent.getRecurringPrice() == null ? null : firstEvent.getRecurringPrice().getPrice(targetCurrency);
             BigDecimal fixedPrice = firstEvent.getFixedPrice() == null ? null : firstEvent.getFixedPrice().getPrice(targetCurrency);
 
             BigDecimal numberOfBillingPeriods;
-            BigDecimal recurringAmount =null;
+            BigDecimal recurringAmount = null;
 
             if (recurringRate != null) {
                 numberOfBillingPeriods = calculateNumberOfBillingPeriods(firstEvent, secondEvent, targetDate);
@@ -177,7 +175,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
             DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
 
-            addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, fixedPrice, targetCurrency);
+            BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
+            addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
     	} catch (CatalogApiException e) {
     		log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
                     invoiceId.toString(),
@@ -185,11 +184,12 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         }
     }
 
-    private void addInvoiceItem(final UUID invoiceId, final List<InvoiceItem> items, final BillingEvent event,
+    private void addInvoiceItem(final UUID invoiceId, final InvoiceItemList items, final BillingEvent event,
                                 final DateTime billThroughDate, final BigDecimal amount, final BigDecimal rate,
                                 final BigDecimal fixedAmount, final Currency currency) {
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(),
-                                  billThroughDate, event.getDescription(), amount, rate, fixedAmount, currency);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(),
+                                  event.getPlan().getName(), event.getPlanPhase().getName(),  event.getEffectiveDate(),
+                                  billThroughDate, amount, rate, fixedAmount, currency);
         items.add(item);
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
index 9e229fb..508fab8 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
@@ -17,8 +17,9 @@
 package com.ning.billing.invoice.model;
 
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.sun.xml.internal.bind.v2.runtime.reflect.Lister;
 import org.joda.time.DateTime;
 
 import javax.annotation.Nullable;
@@ -29,30 +30,34 @@ public class DefaultInvoiceItem implements InvoiceItem {
     private final UUID id;
     private final UUID invoiceId;
     private final UUID subscriptionId;
+    private final String planName;
+    private final String phaseName;
     private DateTime startDate;
     private DateTime endDate;
-    private final String description;
     private BigDecimal recurringAmount;
     private final BigDecimal recurringRate;
     private BigDecimal fixedAmount;
     private final Currency currency;
 
-    public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate,
-                              String description, BigDecimal recurringAmount, BigDecimal recurringRate,
+    public DefaultInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                              DateTime startDate, DateTime endDate,
+                              BigDecimal recurringAmount, BigDecimal recurringRate,
                               BigDecimal fixedAmount, Currency currency) {
-        this(UUID.randomUUID(), invoiceId, subscriptionId, startDate, endDate, description,
+        this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
              recurringAmount, recurringRate, fixedAmount, currency);
     }
 
-    public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, DateTime startDate, DateTime endDate,
-                              String description, BigDecimal recurringAmount, BigDecimal recurringRate,
+    public DefaultInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+                              DateTime startDate, DateTime endDate,
+                              BigDecimal recurringAmount, BigDecimal recurringRate,
                               BigDecimal fixedAmount, Currency currency) {
         this.id = id;
         this.invoiceId = invoiceId;
         this.subscriptionId = subscriptionId;
+        this.planName = planName;
+        this.phaseName = phaseName;
         this.startDate = startDate;
         this.endDate = endDate;
-        this.description = description;
         this.recurringAmount = recurringAmount;
         this.recurringRate = recurringRate;
         this.fixedAmount = fixedAmount;
@@ -63,9 +68,10 @@ public class DefaultInvoiceItem implements InvoiceItem {
         this.id = UUID.randomUUID();
         this.invoiceId = invoiceId;
         this.subscriptionId = that.getSubscriptionId();
+        this.planName = that.getPlanName();
+        this.phaseName = that.getPhaseName();
         this.startDate = that.getStartDate();
         this.endDate = that.getEndDate();
-        this.description = that.getDescription();
         this.recurringAmount = that.getRecurringAmount();
         this.recurringRate = that.getRecurringRate();
         this.fixedAmount = that.getFixedAmount();
@@ -76,7 +82,7 @@ public class DefaultInvoiceItem implements InvoiceItem {
     public InvoiceItem asCredit(UUID invoiceId) {
         BigDecimal recurringAmountNegated = recurringAmount == null ? null : recurringAmount.negate();
         BigDecimal fixedAmountNegated = fixedAmount == null ? null : fixedAmount.negate();
-        return new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, description,
+        return new DefaultInvoiceItem(invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
                                       recurringAmountNegated, recurringRate, fixedAmountNegated, currency);
     }
 
@@ -96,6 +102,16 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
+    public String getPlanName() {
+        return planName;
+    }
+
+    @Override
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    @Override
     public DateTime getStartDate() {
         return startDate;
     }
@@ -106,11 +122,6 @@ public class DefaultInvoiceItem implements InvoiceItem {
     }
 
     @Override
-    public String getDescription() {
-        return description;
-    }
-
-    @Override
     public BigDecimal getRecurringAmount() {
         return recurringAmount;
     }
@@ -144,7 +155,8 @@ public class DefaultInvoiceItem implements InvoiceItem {
                 return 1;
             }
 
-            return getStartDate().compareTo(that.getStartDate());
+            int compareStartDates = getStartDate().compareTo(that.getStartDate());
+            return compareStartDates;
         } else {
             return compareSubscriptions;
         }
@@ -156,18 +168,15 @@ public class DefaultInvoiceItem implements InvoiceItem {
         if (this.startDate.equals(that.getStartDate()) && this.endDate.equals(that.getEndDate())) {
             this.startDate = this.endDate;
                 this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
-                this.fixedAmount = safeSubtract(this.fixedAmount, that.getFixedAmount());
         } else {
             if (this.startDate.equals(that.getStartDate())) {
                 this.startDate = that.getEndDate();
                 this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
-                this.fixedAmount = safeSubtract(this.fixedAmount, that.getFixedAmount());
             }
 
             if (this.endDate.equals(that.getEndDate())) {
                 this.endDate = that.getStartDate();
                 this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
-                this.fixedAmount = safeSubtract(this.fixedAmount, that.getFixedAmount());
             }
         }
     }
@@ -192,13 +201,14 @@ public class DefaultInvoiceItem implements InvoiceItem {
 
     @Override
     public boolean duplicates(InvoiceItem that) {
-        if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
-        
-        //if (!compareNullableBigDecimal(this.getRecurringAmount(), that.getRecurringAmount())) {return false;}
+        if (!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+
+        if (!this.planName.equals(that.getPlanName())) {return false;}
+        if (!this.phaseName.equals(that.getPhaseName())) {return false;}
+
         if (!compareNullableBigDecimal(this.getRecurringRate(), that.getRecurringRate())) {return false;}
-        if (!compareNullableBigDecimal(this.getFixedAmount(), that.getFixedAmount())) {return false;}
 
-        if(!this.getCurrency().equals(that.getCurrency())) {return false;}
+        if (!this.getCurrency().equals(that.getCurrency())) {return false;}
 
         DateRange thisDateRange = new DateRange(this.getStartDate(), this.getEndDate());
         return thisDateRange.contains(that.getStartDate()) && thisDateRange.contains(that.getEndDate());
@@ -247,6 +257,8 @@ public class DefaultInvoiceItem implements InvoiceItem {
         sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
         sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
         sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+        sb.append("planName = ").append(planName).append(", ");
+        sb.append("phaseName = ").append(phaseName).append(", ");
         sb.append("startDate = ").append(startDate.toString()).append(", ");
         sb.append("endDate = ").append(startDate.toString()).append(", ");
         sb.append("recurringAmount = ");
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index 5a10928..13ccb60 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -76,13 +76,26 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
         Iterator<InvoiceItem> iterator = this.iterator();
         while (iterator.hasNext()) {
             InvoiceItem item = iterator.next();
-            Boolean hasZeroRecurringAmount = item.getRecurringAmount() == null || (item.getRecurringAmount().compareTo(BigDecimal.ZERO) == 0);
-            Boolean hasRecurringRate = item.getRecurringRate() == null || (item.getRecurringRate().compareTo(BigDecimal.ZERO) != 0);
-            Boolean hasZeroFixedAmount = item.getFixedAmount() == null || (item.getFixedAmount().compareTo(BigDecimal.ZERO) == 0);
 
-            if (hasZeroRecurringAmount && hasRecurringRate & hasZeroFixedAmount) {
+            boolean fixedAmountNull = (item.getFixedAmount() == null);
+            boolean recurringRateNull = (item.getRecurringRate() == null);
+            boolean recurringAmountZero = (item.getRecurringRate() !=null) && (item.getRecurringAmount().compareTo(BigDecimal.ZERO) == 0);
+
+            if (fixedAmountNull && (recurringRateNull || recurringAmountZero)) {
+                iterator.remove();
+            } else if (item.getStartDate().compareTo(item.getEndDate()) == 0) {
                 iterator.remove();
             }
         }
     }
+
+    public boolean hasInvoiceItemForPhase(final String phaseName) {
+        for (final InvoiceItem item : this) {
+            if (item.getPhaseName().equals(phaseName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index 39fcd24..5b37548 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -4,9 +4,10 @@ invoiceItemFields(prefix) ::= <<
   <prefix>id,
   <prefix>invoice_id,
   <prefix>subscription_id,
+  <prefix>plan_name,
+  <prefix>phase_name,
   <prefix>start_date,
   <prefix>end_date,
-  <prefix>description,
   <prefix>recurring_amount,
   <prefix>recurring_rate,
   <prefix>fixed_amount,
@@ -40,14 +41,14 @@ getInvoiceItemsBySubscription() ::= <<
 
 create() ::= <<
   INSERT INTO invoice_items(<invoiceItemFields()>)
-  VALUES(:id, :invoiceId, :subscriptionId, :startDate, :endDate, :description,
+  VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
          :recurringAmount, :recurringRate, :fixedAmount, :currency);
 >>
 
 update() ::= <<
   UPDATE invoice_items
-  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, start_date = :startDate, end_date = :endDate,
-      description = :description, recurring_amount = :recurringAmount, recurring_rate = :recurringRate,
+  SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+      start_date = :startDate, end_date = :endDate, recurring_amount = :recurringAmount, recurring_rate = :recurringRate,
       fixed_amount = :fixedAmount, currency = :currency
   WHERE id = :id;
 >>
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 348f423..1cf6f23 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -3,9 +3,10 @@ CREATE TABLE invoice_items (
   id char(36) NOT NULL,
   invoice_id char(36) NOT NULL,
   subscription_id char(36) NOT NULL,
+  plan_name varchar(50) NOT NULL,
+  phase_name varchar(50) NOT NULL,
   start_date datetime NOT NULL,
   end_date datetime NULL,
-  description varchar(100) NOT NULL,
   recurring_amount numeric(10,4) NOT NULL,
   recurring_rate numeric(10,4) NOT NULL,
   fixed_amount numeric(10,4) NOT NULL,
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 9338344..2369467 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -84,7 +84,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         UUID subscriptionId = UUID.randomUUID();
         DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
         DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
-        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
+        InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
         invoice.addInvoiceItem(invoiceItem);
         invoiceDao.create(invoice);
 
@@ -181,7 +181,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("9.0");
         BigDecimal amount = rate.multiply(new BigDecimal("3.0"));
 
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, targetDate, endDate, "test", amount, rate, null, Currency.USD);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, null, Currency.USD);
         invoice.addInvoiceItem(item);
         invoiceDao.create(invoice);
 
@@ -276,16 +276,16 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
         DateTime endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, null, Currency.USD);
+        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item3);
 
-        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, null, Currency.USD);
+        DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, null, Currency.USD);
         invoiceItemDao.create(item4);
 
         // create invoice 2 (subscriptions 1-3)
@@ -297,13 +297,13 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         startDate = endDate;
         endDate = startDate.plusMonths(1);
 
-        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item5);
 
-        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item6);
 
-        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, null, Currency.USD);
+        DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item7);
 
         // check that each subscription returns the correct number of invoices
@@ -362,10 +362,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
         BigDecimal payment1 = new BigDecimal("48.0");
@@ -389,10 +389,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
         BigDecimal balance = invoiceDao.getAccountBalance(accountId);
@@ -427,10 +427,10 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate1 = new BigDecimal("17.0");
         BigDecimal rate2 = new BigDecimal("42.0");
 
-        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test A", rate1, rate1, null, Currency.USD);
+        DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
         invoiceItemDao.create(item1);
 
-        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), startDate, endDate, "test B", rate2, rate2, null, Currency.USD);
+        DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
         invoiceItemDao.create(item2);
 
         DateTime upToDate;
@@ -453,7 +453,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         BigDecimal rate3 = new BigDecimal("21.0");
 
-        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoice2.getId(), UUID.randomUUID(), startDate2, endDate2, "test C", rate3, rate3, null, Currency.USD);
+        DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, null, Currency.USD);
         invoiceItemDao.create(item3);
 
         upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index 92a90b3..b2c2f23 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -45,7 +45,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
         BigDecimal rate = new BigDecimal("20.00");
 
-        InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", rate, rate, rate, Currency.USD);
+        InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, rate, Currency.USD);
         invoiceItemDao.create(item);
 
         InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
@@ -55,7 +55,6 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
         assertEquals(thisItem.getStartDate(), item.getStartDate());
         assertEquals(thisItem.getEndDate(), item.getEndDate());
-        assertEquals(thisItem.getDescription(), item.getDescription());
         assertEquals(thisItem.getRecurringAmount().compareTo(item.getRecurringAmount()), 0);
         assertEquals(thisItem.getRecurringRate().compareTo(item.getRecurringRate()), 0);
         assertEquals(thisItem.getFixedAmount().compareTo(item.getFixedAmount()), 0);
@@ -70,7 +69,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
 
         for (int i = 0; i < 3; i++) {
             UUID invoiceId = UUID.randomUUID();
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate.plusMonths(i), startDate.plusMonths(i + 1), "test", rate, rate, null, Currency.USD);
+            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, null, Currency.USD);
             invoiceItemDao.create(item);
         }
 
@@ -87,7 +86,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         for (int i = 0; i < 5; i++) {
             UUID subscriptionId = UUID.randomUUID();
             BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
-            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", amount, amount, amount, Currency.USD);
+            DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, amount, Currency.USD);
             invoiceItemDao.create(item);
         }
 
@@ -108,7 +107,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
         BigDecimal rate = new BigDecimal("20.00");
 
         UUID subscriptionId = UUID.randomUUID();
-        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, rate, Currency.USD);
+        DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, rate, Currency.USD);
         invoiceItemDao.create(item);
 
         List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index c0eb0cc..8cd47ff 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -46,14 +46,12 @@ import org.testng.annotations.Test;
 
 import javax.annotation.Nullable;
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.UUID;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 @Test(groups = {"fast", "invoicing", "invoiceGenerator"})
 public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
@@ -494,9 +492,9 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
 
         InvoiceItemList items = new InvoiceItemList();
         UUID subscriptionId = UUID.randomUUID();
-        InvoiceItem item1 = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, new DateTime("2012-01-30T16:02:04.000-08:00"), new DateTime("2012-01-30T16:02:04.000-08:00"), "1", ZERO, ZERO, ZERO, Currency.USD);
-        InvoiceItem item2 = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, new DateTime("2012-02-29T16:02:04.000-08:00"), new DateTime("2012-02-29T16:02:04.000-08:00"), "2", ZERO, new BigDecimal("249.95"), ZERO, Currency.USD);
-        InvoiceItem item3 = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, new DateTime("2012-01-30T16:02:04.000-08:00"), new DateTime("2012-01-30T16:02:04.000-08:00"), "3", ZERO, ZERO, ZERO, Currency.USD);
+        InvoiceItem item1 = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, shotgun.getName(), shotgunMonthly.getName(), new DateTime("2012-01-30T16:02:04.000-08:00"), new DateTime("2012-01-30T16:02:04.000-08:00"), ZERO, ZERO, ZERO, Currency.USD);
+        InvoiceItem item2 = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, assaultRifle.getName(), assaultRifleMonthly.getName(), new DateTime("2012-02-29T16:02:04.000-08:00"), new DateTime("2012-02-29T16:02:04.000-08:00"), ZERO, new BigDecimal("249.95"), ZERO, Currency.USD);
+        InvoiceItem item3 = new DefaultInvoiceItem(UUID.randomUUID(), subscriptionId, pistol.getName(), pistolMonthlyTrial.getName(), new DateTime("2012-01-30T16:02:04.000-08:00"), new DateTime("2012-01-30T16:02:04.000-08:00"), ZERO, ZERO, ZERO, Currency.USD);
         items.add(item1);
         items.add(item2);
         items.add(item3);
@@ -507,6 +505,42 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
         assertTrue(invoice.getNumberOfItems() > 0);
     }
 
+    @Test
+    public void testImmediateChange() {
+        UUID accountId = UUID.randomUUID();
+        Subscription subscription = new MockSubscription();
+        Plan plan1 = new MockPlan("plan 1");
+        PlanPhase phase1 = new MockPlanPhase(plan1, PhaseType.TRIAL);
+        Plan plan2 = new MockPlan("plan 2");
+        PlanPhase phase2 = new MockPlanPhase(plan2, PhaseType.TRIAL);
+        InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
+        BillingEventSet events = new BillingEventSet();
+
+        BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
+                                                      plan1, phase1,
+                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+                                                      BillingModeType.IN_ADVANCE, "Test Event 1",
+                                                      SubscriptionTransitionType.CREATE);
+        events.add(event1);
+
+        Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
+        assertNotNull(invoice1);
+        assertEquals(invoice1.getNumberOfItems(), 1);
+
+        BillingEvent event2 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
+                                                      plan2, phase2,
+                                                      zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+                                                      BillingModeType.IN_ADVANCE, "Test Event 2",
+                                                      SubscriptionTransitionType.CHANGE);
+        events.add(event2);
+        InvoiceItemList items = new InvoiceItemList(invoice1.getInvoiceItems());
+        Invoice invoice2 = generator.generateInvoice(accountId, events, items, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
+
+        assertNotNull(invoice2);
+        assertEquals(invoice2.getNumberOfItems(), 1);
+        assertEquals(invoice2.getInvoiceItems().get(0).getPlanName(), plan2.getName());
+    }
+
     private MockPlanPhase createMockMonthlyPlanPhase() {
         return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
     }
@@ -517,7 +551,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
     }
 
     private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
-                                                     final BigDecimal fixedRate, PhaseType phaseType) {
+                                                     @Nullable final BigDecimal fixedRate, PhaseType phaseType) {
         return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
                                  new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
                                  BillingPeriod.MONTHLY, phaseType);
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index 22d3c50..b722567 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -68,14 +68,14 @@ public abstract class TestPaymentApi {
         final UUID subscriptionId = UUID.randomUUID();
 
         invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
-                                           subscriptionId,
-                                           now,
-                                           now.plusMonths(1),
-                                           "Test",
-                                           amount,
-                                           new BigDecimal("1.0"),
-                                           null,
-                                           Currency.USD));
+                                                       subscriptionId,
+                                                       "test plan", "test phase",
+                                                       now,
+                                                       now.plusMonths(1),
+                                                       amount,
+                                                       new BigDecimal("1.0"),
+                                                       null,
+                                                       Currency.USD));
 
         List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
 
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index b4df1ba..2bae34b 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -68,9 +68,10 @@ public class TestHelper {
         for (InvoiceItem item : items) {
             invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
                                                item.getSubscriptionId(),
+                                               item.getPlanName(),
+                                               item.getPhaseName(),
                                                item.getStartDate(),
                                                item.getEndDate(),
-                                               item.getDescription(),
                                                item.getRecurringAmount(),
                                                item.getRecurringRate(),
                                                item.getFixedAmount(),
@@ -84,7 +85,7 @@ public class TestHelper {
         final DateTime now = new DateTime(DateTimeZone.UTC);
         final UUID subscriptionId = UUID.randomUUID();
         final BigDecimal amount = new BigDecimal("10.00");
-        final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, now, now.plusMonths(1), "Test", amount, new BigDecimal("1.0"), null, Currency.USD);
+        final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), null, Currency.USD);
 
         return createTestInvoice(account, now, Currency.USD, item);
     }