killbill-aplcache

Merge remote-tracking branch 'origin/invoice2' into new-amount-decimals-handling-rfc Signed-off-by:

3/3/2014 9:15:42 AM

Changes

Details

diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index cc2a894..8a7dbee 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -181,8 +181,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.83")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.83")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.82")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.82")));
 
         // Add 10 days to generate next invoice. We verify that we indeed have a notification for nextBillingDate
         addDaysAndCheckForCompletion(10, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -190,7 +190,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // Item for the upgraded recurring plan
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-104.83")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-104.82")));
 
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 8, 31), callContext);
 
@@ -276,14 +276,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
                                     // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.63")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("166.63")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
 
 
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-166.63")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-169.32")));
 
         // Move one month ahead, and check if we get the next invoice
         addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -378,13 +378,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
                                     // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.63")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("166.63")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
 
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("241.89")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-166.63")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-169.32")));
 
         // Move one month ahead, and check if we get the next invoice
         addDaysAndCheckForCompletion(30, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -465,24 +465,24 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.83")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.83")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.82")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.82")));
 
         // Do an upgrade now
         checkChangePlanWithOverdueState(baseEntitlement, false, false);
 
         invoiceChecker.checkRepairedInvoice(account.getId(), 3, callContext,
                                             new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.83")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.83")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-64.51")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("64.51")));
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.82")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.82")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-64.50")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("64.50")));
 
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // Item for the upgraded recurring plan
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("154.85")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("154.83")),
                                     // Repair for upgrade
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("-154.85")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("-154.83")));
 
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
@@ -643,8 +643,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
                                     // Item for the upgraded recurring plan
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-76.59")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("76.59")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
 
 
 
@@ -654,18 +654,18 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         invoiceChecker.checkRepairedInvoice(account.getId(), 3, callContext,
                                             new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-76.59")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("76.59")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-48.37")),
-                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("48.37")));
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-48.38")),
+                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("48.38")));
 
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("116.09")),
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("-116.09")));
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("116.12")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("-116.12")));
 
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
-        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-12.91")), 0);
+        assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-12.89")), 0);
     }
 
     @Test(groups = "slow", description = "Test overdue from non paid external charge")
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
new file mode 100644
index 0000000..2e7a912
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2010-2014 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.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import com.ning.billing.catalog.api.BillingActionPolicy;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.DefaultEntitlement;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.util.tag.ControlTagType;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationBase {
+
+    @Test(groups = "slow")
+    public void testChangeMonthlyToAnnual() throws Exception {
+
+        // We take april as it has 30 days (easier to play with BCD)
+        final LocalDate today = new LocalDate(2012, 4, 1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Shotgun";
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(31);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        //
+        // MOVE MONTHLY TO ANNUAL
+        //
+        clock.addDays(10);
+
+        changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT);
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 3);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("161.26")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-161.26")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2327.62")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("-161.26")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+    }
+
+    @Test(groups = "slow")
+    public void testChangeMonthlyToQuarterly() throws Exception {
+
+        // We take april as it has 30 days (easier to play with BCD)
+        final LocalDate today = new LocalDate(2012, 4, 1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Pistol";
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(31);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        //
+        // MOVE MONTHLY TO QUARTERLY
+        //
+        clock.addDays(10);
+
+        changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.QUARTERLY, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT);
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 3);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("19.32")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.32")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 8, 1), InvoiceItemType.RECURRING, new BigDecimal("61.59")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("-19.32")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+        // Move to 1020-08-01
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(20);
+        clock.addMonths(2);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 4);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 11, 1), InvoiceItemType.RECURRING, new BigDecimal("69.95")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
+
+    }
+
+    @Test(groups = "slow")
+    public void testPauseResumeAnnual() throws Exception {
+
+        // We take april as it has 30 days (easier to play with BCD)
+        final LocalDate today = new LocalDate(2012, 4, 1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Shotgun";
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(31);
+        assertListenerStatus();
+
+        // 2012-5-12
+        clock.addDays(10);
+
+        busHandler.pushExpectedEvents(NextEvent.PAUSE, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
+        entitlementApi.pause(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), callContext);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("2327.62")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2327.62")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        // 2012-6-4
+        clock.addDays(23);
+
+        busHandler.pushExpectedEvents(NextEvent.RESUME, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT);
+        entitlementApi.resume(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), callContext);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 3);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2013, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2380.22")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2012, 6, 4), InvoiceItemType.CBA_ADJ, new BigDecimal("-2327.62")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addYears(1);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 4);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 6, 1), new LocalDate(2014, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
+    }
+
+    @Test(groups = "slow")
+    public void testPauseResumeAnnualWithInvoicingOff() throws Exception {
+
+        // We take april as it has 30 days (easier to play with BCD)
+        final LocalDate today = new LocalDate(2012, 4, 1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Shotgun";
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(31);
+        assertListenerStatus();
+
+        // Auto invoice off
+        busHandler.pushExpectedEvents(NextEvent.TAG);
+        tagUserApi.addTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
+        assertListenerStatus();
+
+        // 2012-5-12
+        clock.addDays(10);
+
+        busHandler.pushExpectedEvents(NextEvent.PAUSE, NextEvent.BLOCK);
+        entitlementApi.pause(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), callContext);
+        assertListenerStatus();
+
+        // 2012-6-4
+        clock.addDays(23);
+        busHandler.pushExpectedEvents(NextEvent.RESUME, NextEvent.BLOCK);
+        entitlementApi.resume(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), callContext);
+        assertListenerStatus();
+
+
+        busHandler.pushExpectedEvents(NextEvent.TAG, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+        tagUserApi.removeTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 3);
+
+
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2012, 6, 4), InvoiceItemType.CBA_ADJ, new BigDecimal("2327.62")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2327.62")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2013, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2380.22")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2012, 6, 4), InvoiceItemType.CBA_ADJ, new BigDecimal("-2327.62")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addYears(1);
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 4);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2013, 6, 1), new LocalDate(2014, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestSubscription.java
index 139e004..b11023a 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestSubscription.java
@@ -109,8 +109,8 @@ public class TestSubscription extends TestIntegrationBase {
         invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
 
         toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
-                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("137.70")),
-                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-137.70")));
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2334.20")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-2334.20")));
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
 
     }
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
index d6531b1..e7e47d2 100644
--- a/beatrix/src/test/resources/catalogSample.xml
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -322,6 +322,40 @@
                 </recurringPrice>
             </finalPhase>
         </plan>
+        <plan name="pistol-quarterly">
+            <product>Pistol</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+                    <fixedPrice> <!-- empty price implies $0 -->
+                    </fixedPrice>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <billingPeriod>QUARTERLY</billingPeriod>
+                <recurringPrice>
+                    <price>
+                        <currency>GBP</currency>
+                        <value>69.95</value>
+                    </price>
+                    <price>
+                        <currency>EUR</currency>
+                        <value>69.95</value>
+                    </price>
+                    <price>
+                        <currency>USD</currency>
+                        <value>69.95</value>
+                    </price>
+                </recurringPrice>
+            </finalPhase>
+        </plan>
         <plan name="pistol-annual">
             <product>Pistol</product>
             <initialPhases>
@@ -867,6 +901,7 @@
                 <plan>shotgun-monthly</plan>
                 <plan>assault-rifle-monthly</plan>
                 <plan>pistol-annual</plan>
+                <plan>pistol-quarterly</plan>
                 <plan>shotgun-annual</plan>
                 <plan>assault-rifle-annual</plan>
                 <plan>laser-scope-monthly</plan>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/com/ning/billing/invoice/generator/BillingIntervalDetail.java
index c54a084..8c1d9cc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/BillingIntervalDetail.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/BillingIntervalDetail.java
@@ -20,10 +20,9 @@ import org.joda.time.LocalDate;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 
-public class BillingIntervalDetail {
-
+import com.google.common.annotations.VisibleForTesting;
 
-    private final boolean shouldUsePatch = true;
+public class BillingIntervalDetail {
 
     private final LocalDate startDate;
     private final LocalDate endDate;
@@ -68,7 +67,8 @@ public class BillingIntervalDetail {
         calculateLastBillingCycleDate();
     }
 
-    private void calculateFirstBillingCycleDate() {
+    @VisibleForTesting
+    void calculateFirstBillingCycleDate() {
 
         final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue();
         final LocalDate billingCycleDate;
@@ -78,11 +78,12 @@ public class BillingIntervalDetail {
             billingCycleDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), billingCycleDay, startDate.getChronology());
         }
 
+        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         LocalDate proposedDate = billingCycleDate;
         while (proposedDate.isBefore(startDate)) {
-            proposedDate = proposedDate.plusMonths(1);
+            proposedDate = proposedDate.plusMonths(numberOfMonthsInPeriod);
         }
-        firstBillingCycleDate = proposedDate;
+        firstBillingCycleDate = alignProposedBillCycleDate(proposedDate);
     }
 
     private void calculateEffectiveEndDate() {
@@ -144,9 +145,6 @@ public class BillingIntervalDetail {
     // We start from a billCycleDate
     //
     private LocalDate alignProposedBillCycleDate(final LocalDate proposedDate) {
-        if (!shouldUsePatch) {
-            return proposedDate;
-        }
         final int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
 
         int proposedBillCycleDate = proposedDate.getDayOfMonth();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
index 8aaa7e7..49a8c2d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
@@ -32,11 +32,11 @@ import com.google.common.collect.Iterables;
 
 /**
  * Tree of invoice items for a given account.
- *
+ * <p/>
  * <p>It contains a map of <tt>SubscriptionItemTree</tt> and the logic is executed independently for all items
  * associated to a given subscription. That also means that invoice item adjustment which cross subscriptions
  * can't be correctly handled when they compete with other forms of adjustments.
- *
+ * <p/>
  * <p>The class is not thread safe, there is no such use case today, and there is a lifecyle to respect:
  * <ul>
  * <li>Add existing invoice items
@@ -50,6 +50,7 @@ public class AccountItemTree {
     private final UUID accountId;
     private final Map<UUID, SubscriptionItemTree> subscriptionItemTree;
     private final List<InvoiceItem> allExistingItems;
+    private List<InvoiceItem> pendingItemAdj;
 
     private boolean isBuilt;
 
@@ -58,6 +59,7 @@ public class AccountItemTree {
         this.subscriptionItemTree = new HashMap<UUID, SubscriptionItemTree>();
         this.isBuilt = false;
         this.allExistingItems = new LinkedList<InvoiceItem>();
+        this.pendingItemAdj = new LinkedList<InvoiceItem>();
     }
 
     /**
@@ -65,30 +67,58 @@ public class AccountItemTree {
      */
     public void build() {
         Preconditions.checkState(!isBuilt);
+
+        if (pendingItemAdj.size() > 0) {
+            for (InvoiceItem item : pendingItemAdj) {
+                addExistingItem(item, true);
+            }
+            pendingItemAdj.clear();
+        }
         for (SubscriptionItemTree tree : subscriptionItemTree.values()) {
             tree.build();
         }
         isBuilt = true;
     }
+
     /**
      * Populate tree from existing items on disk
      *
      * @param existingItem an item read on disk
      */
     public void addExistingItem(final InvoiceItem existingItem) {
+        addExistingItem(existingItem, false);
+    }
+
+    private void addExistingItem(final InvoiceItem existingItem, boolean failOnMissingSubscription) {
 
         Preconditions.checkState(!isBuilt);
-        if (existingItem.getInvoiceItemType() != InvoiceItemType.RECURRING &&
-            existingItem.getInvoiceItemType() != InvoiceItemType.REPAIR_ADJ &&
-            existingItem.getInvoiceItemType() != InvoiceItemType.FIXED &&
-            existingItem.getInvoiceItemType() != InvoiceItemType.ITEM_ADJ) {
-            return;
+        switch (existingItem.getInvoiceItemType()) {
+            case EXTERNAL_CHARGE:
+            case CBA_ADJ:
+            case CREDIT_ADJ:
+            case REFUND_ADJ:
+                return;
+
+            case RECURRING:
+            case REPAIR_ADJ:
+            case FIXED:
+            case ITEM_ADJ:
+                break;
+
+            default:
+                Preconditions.checkState(false, "Unknown invoice item type " + existingItem.getInvoiceItemType());
+
         }
 
         allExistingItems.add(existingItem);
 
-        final UUID subscriptionId  = getSubscriptionId(existingItem, allExistingItems);
-        Preconditions.checkNotNull(subscriptionId);
+        final UUID subscriptionId = getSubscriptionId(existingItem, allExistingItems);
+        Preconditions.checkState(subscriptionId != null || !failOnMissingSubscription);
+
+        if (subscriptionId == null && existingItem.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ) {
+            pendingItemAdj.add(existingItem);
+            return;
+        }
 
         if (!subscriptionItemTree.containsKey(subscriptionId)) {
             subscriptionItemTree.put(subscriptionId, new SubscriptionItemTree(subscriptionId));
@@ -104,13 +134,13 @@ public class AccountItemTree {
      */
     public void mergeWithProposedItems(final List<InvoiceItem> proposedItems) {
 
+        build();
         for (SubscriptionItemTree tree : subscriptionItemTree.values()) {
             tree.flatten(true);
         }
-        isBuilt = true;
 
         for (InvoiceItem item : proposedItems) {
-            final UUID subscriptionId  = getSubscriptionId(item, null);
+            final UUID subscriptionId = getSubscriptionId(item, null);
             SubscriptionItemTree tree = subscriptionItemTree.get(subscriptionId);
             if (tree == null) {
                 tree = new SubscriptionItemTree(subscriptionId);
@@ -125,7 +155,6 @@ public class AccountItemTree {
     }
 
     /**
-     *
      * @return the resulting list of items that should be written to disk
      */
     public List<InvoiceItem> getResultingItemList() {
@@ -148,13 +177,13 @@ public class AccountItemTree {
             item.getInvoiceItemType() == InvoiceItemType.FIXED) {
             return item.getSubscriptionId();
         } else {
-            final InvoiceItem linkedItem  = Iterables.tryFind(allItems, new Predicate<InvoiceItem>() {
+            final InvoiceItem linkedItem = Iterables.tryFind(allItems, new Predicate<InvoiceItem>() {
                 @Override
                 public boolean apply(final InvoiceItem input) {
                     return item.getLinkedItemId().equals(input.getId());
                 }
-            }).get();
-            return linkedItem.getSubscriptionId();
+            }).orNull();
+            return linkedItem != null ? linkedItem.getSubscriptionId() : null;
         }
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
index fa7bf04..2ead948 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
@@ -92,6 +92,28 @@ public class ItemsInterval {
      * @param mergeMode
      */
     public void buildFromItems(final List<Item> output, final boolean mergeMode) {
+        final Item item  = getResultingItem(mergeMode);
+        if (item != null) {
+            output.add(item);
+        }
+    }
+
+    private Item getResultingItem(final boolean mergeMode) {
+        return mergeMode ? getResulting_CANCEL_Item() : getResulting_ADD_Item();
+    }
+
+    private Item getResulting_CANCEL_Item() {
+        Preconditions.checkState(items.size() == 0 || items.size() == 1);
+        return Iterables.tryFind(items, new Predicate<Item>() {
+            @Override
+            public boolean apply(final Item input) {
+                return input.getAction() == ItemAction.CANCEL;
+            }
+        }).orNull();
+    }
+
+
+    private Item getResulting_ADD_Item() {
 
         final Set<UUID> repairedIds = new HashSet<UUID>();
         final ListIterator<Item> it = items.listIterator(items.size());
@@ -100,20 +122,13 @@ public class ItemsInterval {
             final Item cur = it.previous();
             switch (cur.getAction()) {
                 case ADD:
-                    // Don't consider ADD items in mergeMode as they are only there to specify the bounderies of the repair elements.
-                    if (!mergeMode) {
-                        // If we found a CANCEL item pointing to that item then don't return it as it was repair (full repair scenario)
-                        if (!repairedIds.contains(cur.getId())) {
-                            output.add(cur);
-                        }
+                    // If we found a CANCEL item pointing to that item then don't return it as it was repair (full repair scenario)
+                    if (!repairedIds.contains(cur.getId())) {
+                        return cur;
                     }
                     break;
 
                 case CANCEL:
-                    // In merge logic we want to CANCEL (repair) items)
-                    if (mergeMode) {
-                        output.add(cur);
-                    }
                     // In all cases populate the set with the id of target item being repaired
                     if (cur.getLinkedId() != null) {
                         repairedIds.add(cur.getLinkedId());
@@ -121,8 +136,10 @@ public class ItemsInterval {
                     break;
             }
         }
+        return null;
     }
 
+
     // Just ensure that ADD items precedes CANCEL items
     public void insertSortedItem(final Item item) {
         items.add(item);
@@ -163,17 +180,11 @@ public class ItemsInterval {
      */
     private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean mergeMode) {
 
-        final List<Item> itemToConsider = new LinkedList<Item>();
-        buildFromItems(itemToConsider, mergeMode);
-        if (itemToConsider.size() == 0) {
+        final Item item  = getResultingItem(mergeMode);
+        if (item == null) {
             return null;
         }
 
-        Preconditions.checkState(itemToConsider.size() == 1);
-        final Item item = itemToConsider.size() == 1 ? itemToConsider.get(0) : null;
-        Preconditions.checkState((!mergeMode && item.getAction() == ItemAction.ADD) ||
-                                 (mergeMode && item.getAction() == ItemAction.CANCEL));
-
         final Item result = new Item(item.toProratedInvoiceItem(startDate, endDate), item.getAction());
         if (item.getAction() == ItemAction.CANCEL && result != null) {
             item.incrementCurrentRepairedAmount(result.getAmount());
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java
new file mode 100644
index 0000000..3762783
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2010-2014 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.tree;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.util.jackson.ObjectMapper;
+
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.google.common.base.Preconditions;
+
+public class ItemsNodeInterval extends NodeInterval {
+
+    private ItemsInterval items;
+
+    public ItemsNodeInterval() {
+        this.items = new ItemsInterval(this);
+    }
+
+    public ItemsNodeInterval(final NodeInterval parent, final Item item) {
+        super(parent, item.getStartDate(), item.getEndDate());
+        this.items = new ItemsInterval(this, item);
+    }
+
+    @JsonIgnore
+    public ItemsInterval getItemsInterval() {
+        return items;
+    }
+
+    public List<Item> getItems() {
+        return items.getItems();
+    }
+
+    /**
+     * <p/>
+     * There is no limit in the depth of the tree,
+     * and the build strategy is to first consider the lowest child for a given period
+     * and go up the tree adding missing interval if needed. For e.g, one of the possible scenario:
+     * <pre>
+     * D1                                                  D2
+     * |---------------------------------------------------|   Plan P1
+     *       D1'             D2'
+     *       |---------------|/////////////////////////////|   Plan P2, REPAIR
+     *
+     *  In that case we will generate:
+     *  [D1,D1') on Plan P1; [D1', D2') on Plan P2, and [D2', D2) repair item
+     *
+     * <pre/>
+     *
+     * In the merge mode, the strategy is different, the tree is fairly shallow
+     * and the goal is to generate the repair items; @see addProposedItem
+     *
+     * @param output result list of built items
+     */
+    public void buildForExistingItems(final List<Item> output) {
+        build(new BuildNodeCallback() {
+            @Override
+            public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                items.buildForMissingInterval(startDate, endDate, output, false);
+            }
+
+            @Override
+            public void onLastNode(final NodeInterval curNode) {
+                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                items.buildFromItems(output, false);
+            }
+        });
+    }
+
+    /**
+     * The merge tree is initially constructed by flattening all the existing items and reversing them (CANCEL node).
+     * <p/>
+     * That means that if we were to not merge any new proposed items, we would end up with only those reversed existing
+     * items, and they would all end up repaired-- which is what we want.
+     * <p/>
+     * However, if there are new proposed items, then we look to see if they are children one our existing reverse items
+     * so that we can generate the repair pieces missing. For e.g, below is one scenario among so many:
+     * <p/>
+     * <pre>
+     * D1                                                  D2
+     * |---------------------------------------------------| (existing reversed (CANCEL) item
+     *       D1'             D2'
+     *       |---------------| (proposed same plan)
+     * </pre>
+     * In that case we want to generated a repair for [D1, D1') and [D2',D2)
+     * <p/>
+     * Note that this tree is never very deep, only 3 levels max, with exiting at the first level
+     * and proposed that are the for the exact same plan but for different dates below.
+     *
+     * @param output result list of built items
+     */
+    public void mergeExistingAndProposed(final List<Item> output) {
+        build(new BuildNodeCallback() {
+            @Override
+            public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                items.buildForMissingInterval(startDate, endDate, output, true);
+            }
+
+            @Override
+            public void onLastNode(final NodeInterval curNode) {
+                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                items.buildFromItems(output, true);
+            }
+        });
+    }
+
+    /**
+     * Add existing item into the tree.
+     *
+     * @param newNode an existing item
+     */
+    public boolean addExistingItem(final ItemsNodeInterval newNode) {
+
+        return addNode(newNode, new AddNodeCallback() {
+            @Override
+            public boolean onExistingNode(final NodeInterval existingNode) {
+                if (!existingNode.isRoot() && newNode.getStart().compareTo(existingNode.getStart()) == 0 && newNode.getEnd().compareTo(existingNode.getEnd()) == 0) {
+                    final Item item = newNode.getItems().get(0);
+                    final ItemsInterval existingOrNewNodeItems = ((ItemsNodeInterval) existingNode).getItemsInterval();
+                    existingOrNewNodeItems.insertSortedItem(item);
+                }
+                // There is no new node added but instead we just populated the list of items for the already existing node.
+                return false;
+            }
+
+            @Override
+            public boolean shouldInsertNode(final NodeInterval insertionNode) {
+                // Always want to insert node in the tree when we find the right place.
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Add proposed item into the (flattened and reversed) tree.
+     *
+     * @param newNode a new proposed item
+     * @return true if the item was merged and will trigger a repair or false if the proposed item should be kept as such
+     *         and no repair generated.
+     */
+    public boolean addProposedItem(final ItemsNodeInterval newNode) {
+
+        return addNode(newNode, new AddNodeCallback() {
+            @Override
+            public boolean onExistingNode(final NodeInterval existingNode) {
+                if (!shouldInsertNode(existingNode)) {
+                    return false;
+                }
+
+                Preconditions.checkState(newNode.getStart().compareTo(existingNode.getStart()) == 0 && newNode.getEnd().compareTo(existingNode.getEnd()) == 0);
+                final Item item = newNode.getItems().get(0);
+                final ItemsInterval existingOrNewNodeItems = ((ItemsNodeInterval) existingNode).getItemsInterval();
+                existingOrNewNodeItems.cancelItems(item);
+                // In the merge logic, whether we really insert the node or find an existing node on which to insert items should be seen
+                // as an insertion (so as to avoid keeping that proposed item, see how return value of addProposedItem is used)
+                return true;
+            }
+
+            @Override
+            public boolean shouldInsertNode(final NodeInterval insertionNode) {
+                // The root level is solely for the reversed existing items. If there is a new node that does not fit below the level
+                // of reversed existing items, we want to return false and keep it outside of the tree. It should be 'kept as such'.
+                if (insertionNode.isRoot()) {
+                    return false;
+                }
+
+                final ItemsInterval insertionNodeItems = ((ItemsNodeInterval) insertionNode).getItemsInterval();
+                Preconditions.checkState(insertionNodeItems.getItems().size() == 1, "Expected existing node to have only one item");
+                final Item insertionNodeItem = insertionNodeItems.getItems().get(0);
+                final Item newNodeItem = newNode.getItems().get(0);
+
+                // If we receive a new proposed that is the same kind as the reversed existing we want to insert it to generate
+                // a piece of repair
+                if (insertionNodeItem.isSameKind(newNodeItem)) {
+                    return true;
+                    // If not, then keep the proposed outside of the tree.
+                } else {
+                    return false;
+                }
+            }
+        });
+    }
+
+    /**
+     * Add the adjustment amount on the item specified by the targetId.
+     *
+     * @param adjustementDate date of the adjustment
+     * @param amount          amount of the adjustment
+     * @param targetId        item that has been adjusted
+     */
+    public void addAdjustment(final LocalDate adjustementDate, final BigDecimal amount, final UUID targetId) {
+        // TODO we should really be using findNode(adjustementDate, new SearchCallback() instead but wrong dates in test
+        // creates test panic.
+        final NodeInterval node = findNode(new SearchCallback() {
+            @Override
+            public boolean isMatch(final NodeInterval curNode) {
+                return ((ItemsNodeInterval) curNode).getItemsInterval().containsItem(targetId);
+            }
+        });
+        Preconditions.checkNotNull(node, "Cannot add adjustement for item = " + targetId + ", date = " + adjustementDate);
+        ((ItemsNodeInterval) node).setAdjustment(amount.negate(), targetId);
+    }
+
+    public void jsonSerializeTree(final ObjectMapper mapper, final OutputStream output) throws IOException {
+
+        final JsonGenerator generator = mapper.getFactory().createJsonGenerator(output);
+        generator.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
+
+        walkTree(new WalkCallback() {
+
+            private int curDepth = 0;
+
+            @Override
+            public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
+                final ItemsNodeInterval node = (ItemsNodeInterval) curNode;
+                if (node.isRoot()) {
+                    return;
+                }
+
+                try {
+                    if (curDepth < depth) {
+                        generator.writeStartArray();
+                        curDepth = depth;
+                    } else if (curDepth > depth) {
+                        generator.writeEndArray();
+                        curDepth = depth;
+                    }
+                    generator.writeObject(node);
+                } catch (IOException e) {
+                    throw new RuntimeException("Failed to deserialize tree", e);
+                }
+            }
+        });
+        generator.close();
+    }
+
+    protected void setAdjustment(final BigDecimal amount, final UUID linkedId) {
+        items.setAdjustment(amount, linkedId);
+    }
+
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
index de7fd1b..b1d1483 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
@@ -16,66 +16,46 @@
 
 package com.ning.billing.invoice.tree;
 
-import java.math.BigDecimal;
 import java.util.List;
-import java.util.UUID;
 
 import org.joda.time.LocalDate;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 
 public class NodeInterval {
 
-    private LocalDate start;
-    private LocalDate end;
-    private ItemsInterval items;
+    protected NodeInterval parent;
+    protected NodeInterval leftChild;
+    protected NodeInterval rightSibling;
 
-    private NodeInterval parent;
-    private NodeInterval leftChild;
-    private NodeInterval rightSibling;
+    protected LocalDate start;
+    protected LocalDate end;
 
     public NodeInterval() {
-        this.items = new ItemsInterval(this);
+        this(null, null, null);
     }
 
-    public NodeInterval(final NodeInterval parent, final Item item) {
-        this.start = item.getStartDate();
-        this.end = item.getEndDate();
-        this.items = new ItemsInterval(this, item);
+    public NodeInterval(final NodeInterval parent, final LocalDate startDate, final LocalDate endDate) {
+        this.start = startDate;
+        this.end = endDate;
         this.parent = parent;
         this.leftChild = null;
         this.rightSibling = null;
     }
 
     /**
-     * Build the output list from the elements in the tree.
-     * <p/>
-     * In the simple mode, mergeMode = false, there is no limit in the depth of the tree,
-     * and the build strategy is to first consider the lowest child for a given period
-     * and go up the tree adding missing interval if needed. For e.g, one of the possible scenario:
-     * <pre>
-     * D1                                                  D2
-     * |---------------------------------------------------|   Plan P1
-     *       D1'             D2'
-     *       |---------------|/////////////////////////////|   Plan P2, REPAIR
+     * Build the tree by calling the callback on the last node in the tree or remaining part with no children.
      *
-     *  In that case we will generate:
-     *  [D1,D1') on Plan P1; [D1', D2') on Plan P2, and [D2', D2) repair item
-     *
-     * <pre/>
-     *
-     * In the merge mode, the strategy is different, the tree is fairly shallow
-     * and the goal is to generate the repair items; @see mergeProposedItem
-     *
-     * @param output    result list of items
-     * @param mergeMode mode used to produce output list
+     * @param callback the callback which perform the build logic.
      */
-    public void build(final List<Item> output, boolean mergeMode) {
+    public void build(final BuildNodeCallback callback) {
+
+        Preconditions.checkNotNull(callback);
 
-        // There is no sub-interval, just add our own items.
         if (leftChild == null) {
-            items.buildFromItems(output, mergeMode);
+            callback.onLastNode(this);
             return;
         }
 
@@ -83,147 +63,183 @@ public class NodeInterval {
         NodeInterval curChild = leftChild;
         while (curChild != null) {
             if (curChild.getStart().compareTo(curDate) > 0) {
-                items.buildForMissingInterval(curDate, curChild.getStart(), output, mergeMode);
+                callback.onMissingInterval(this, curDate, curChild.getStart());
             }
-            curChild.build(output, mergeMode);
+            curChild.build(callback);
             curDate = curChild.getEnd();
             curChild = curChild.getRightSibling();
         }
+
+        // Finally if there is a hole at the end, we build the missing piece from ourself
         if (curDate.compareTo(end) < 0) {
-            items.buildForMissingInterval(curDate, end, output, mergeMode);
+            callback.onMissingInterval(this, curDate, end);
         }
     }
 
     /**
-     * The merge tree is initially constructed by flattening all the existing items and reversing them (CANCEL node).
-     * That means that if we were to not merge any new proposed items, we would end up with only those reversed existing
-     * items, and they would all end up repaired-- which is what we want.
-     * <p/>
-     * However, if there are new proposed items, then we look to see if they are children one our existing reverse items
-     * so that we can generate the repair pieces missing. For e.g, below is one scenario among so many:
-     * <p/>
-     * <pre>
-     * D1                                                  D2
-     * |---------------------------------------------------| (existing reversed (CANCEL) item
-     *       D1'             D2'
-     *       |---------------| (proposed same plan)
-     * </pre>
-     * In that case we want to generated a repair for [D1, D1') and [D2',D2)
-     * <p/>
-     * Note that this tree is never very deep, only 3 levels max, with exiting at the first level
-     * and proposed that are the for the exact same plan but for different dates below.
+     * Add a new node in the tree.
      *
-     * @param newNode a new proposed item
-     * @return true if the item was merged and will trigger a repair or false if the proposed item should be kept as such
-     *         and no repair generated.
+     * @param newNode  the node to be added
+     * @param callback the callback that will allow to specify insertion and return behavior.
+     * @return true if node was inserted. Note that this is driven by the callback, this method is generic
+     *         and specific behavior can be tuned through specific callbacks.
      */
-    public boolean mergeProposedItem(final NodeInterval newNode) {
+    public boolean addNode(final NodeInterval newNode, final AddNodeCallback callback) {
 
-        Preconditions.checkState(newNode.getItems().size() == 1, "Expected new node to have only one item");
-        final Item newNodeItem = newNode.getItems().get(0);
+        Preconditions.checkNotNull(newNode);
+        Preconditions.checkNotNull(callback);
 
-        if (!isRoot() && newNodeItem.getStartDate().compareTo(start) == 0 && newNodeItem.getEndDate().compareTo(end) == 0) {
-            items.cancelItems(newNodeItem);
-            return true;
+        if (!isRoot() && newNode.getStart().compareTo(start) == 0 && newNode.getEnd().compareTo(end) == 0) {
+            return callback.onExistingNode(this);
         }
+
         computeRootInterval(newNode);
 
+        newNode.parent = this;
         if (leftChild == null) {
-            // There is no existing items, only new proposed one, nothing to add in that merge tree
-            if (isRoot()) {
-                return false;
-            } else {
-                // Proposed item is the first child of an existing item with the same product info.
+            if (callback.shouldInsertNode(this)) {
                 leftChild = newNode;
                 return true;
-
+            } else {
+                return false;
             }
         }
 
         NodeInterval prevChild = null;
         NodeInterval curChild = leftChild;
-        do {
-            if (curChild.isItemContained(newNodeItem)) {
-                final Item existingNodeItem = curChild.getItems().get(0);
+        while (curChild != null) {
+            if (curChild.isItemContained(newNode)) {
+                return curChild.addNode(newNode, callback);
+            }
 
-                Preconditions.checkState(curChild.getItems().size() == 1, "Expected existing node to have only one item");
-                if (existingNodeItem.isSameKind(newNodeItem)) {
-                    // Proposed item has same product info than parent and is contained so insert it at the right place in the tree
-                    curChild.mergeProposedItem(newNode);
+            if (curChild.isItemOverlap(newNode)) {
+                if (callback.shouldInsertNode(this)) {
+                    rebalance(newNode);
                     return true;
                 } else {
                     return false;
                 }
             }
 
-            // STEPH test for that code path
-            if (newNodeItem.getStartDate().compareTo(curChild.getStart()) < 0) {
-                newNode.rightSibling = curChild;
-                if (prevChild == null) {
-                    leftChild = newNode;
+            if (newNode.getStart().compareTo(curChild.getStart()) < 0) {
+                if (callback.shouldInsertNode(this)) {
+                    newNode.rightSibling = curChild;
+                    if (prevChild == null) {
+                        leftChild = newNode;
+                    } else {
+                        prevChild.rightSibling = newNode;
+                    }
+                    return true;
                 } else {
-                    prevChild.rightSibling = newNode;
+                    return false;
                 }
-                return true;
             }
-
             prevChild = curChild;
             curChild = curChild.rightSibling;
-        } while (curChild != null);
+        }
 
-        if (isRoot()) {
-            // The new proposed item spans over a new interval, nothing to add in the merge tree
-            return false;
-        } else {
+        if (callback.shouldInsertNode(this)) {
             prevChild.rightSibling = newNode;
             return true;
+        } else {
+            return false;
         }
     }
 
     /**
-     * Add an existing item in the tree of items.
+     * Return the first node satisfying the date and match callback.
      *
-     * @param newNode new existing item to be added
+     * @param targetDate target date for possible match nodes whose interval comprises that date
+     * @param callback   custom logic to decide if a given node is a match
+     * @return the found node or null if there is nothing.
      */
-    public void addExistingItem(final NodeInterval newNode) {
-        final Item item = newNode.getItems().get(0);
-        if (!isRoot() && item.getStartDate().compareTo(start) == 0 && item.getEndDate().compareTo(end) == 0) {
-            items.insertSortedItem(item);
-            return;
+    public NodeInterval findNode(final LocalDate targetDate, final SearchCallback callback) {
+
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(targetDate);
+
+        if (targetDate.compareTo(getStart()) < 0 || targetDate.compareTo(getEnd()) > 0) {
+            return null;
         }
-        computeRootInterval(newNode);
-        addNode(newNode);
+
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            if (curChild.getStart().compareTo(targetDate) <= 0 && curChild.getEnd().compareTo(targetDate) >= 0) {
+                if (callback.isMatch(curChild)) {
+                    return curChild;
+                }
+                NodeInterval result = curChild.findNode(targetDate, callback);
+                if (result != null) {
+                    return result;
+                }
+            }
+            curChild = curChild.getRightSibling();
+        }
+        return null;
     }
 
     /**
-     * Add the adjustment amount on the item specified by the targetId.
+     * Return the first node satisfying the date and match callback.
      *
-     * @param adjustementDate date of the adjustment
-     * @param amount amount of the adjustment
-     * @param targetId item that has been adjusted
+     * @param callback custom logic to decide if a given node is a match
+     * @return the found node or null if there is nothing.
      */
-    public void addAdjustment(final LocalDate adjustementDate, final BigDecimal amount, final UUID targetId) {
-        NodeInterval node = findNode(adjustementDate, targetId);
-        Preconditions.checkNotNull(node, "Cannot add adjustement for item = " + targetId + ", date = " + adjustementDate);
-        node.setAdjustment(amount.negate(), targetId);
+    public NodeInterval findNode(final SearchCallback callback) {
+
+        Preconditions.checkNotNull(callback);
+        if (callback.isMatch(this)) {
+            return this;
+        }
+
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            final NodeInterval result = curChild.findNode(callback);
+            if (result != null) {
+                return result;
+            }
+            curChild = curChild.getRightSibling();
+        }
+        return null;
     }
 
-    public boolean isItemContained(final Item item) {
-        return (item.getStartDate().compareTo(start) >= 0 &&
-                item.getStartDate().compareTo(end) <= 0 &&
-                item.getEndDate().compareTo(start) >= 0 &&
-                item.getEndDate().compareTo(end) <= 0);
+    /**
+     * Walk the tree (depth first search) and invoke callback for each node.
+     *
+     * @param callback
+     */
+    public void walkTree(WalkCallback callback) {
+        Preconditions.checkNotNull(callback);
+        walkTreeWithDepth(callback, 0);
     }
 
-    public boolean isItemOverlap(final Item item) {
-        return ((item.getStartDate().compareTo(start) < 0 &&
-                 item.getEndDate().compareTo(end) >= 0) ||
-                (item.getStartDate().compareTo(start) <= 0 &&
-                 item.getEndDate().compareTo(end) > 0));
+    private void walkTreeWithDepth(WalkCallback callback, int depth) {
+
+        Preconditions.checkNotNull(callback);
+        callback.onCurrentNode(depth, this, parent);
+
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            curChild.walkTreeWithDepth(callback, (depth + 1));
+            curChild = curChild.getRightSibling();
+        }
     }
 
 
+    public boolean isItemContained(final NodeInterval newNode) {
+        return (newNode.getStart().compareTo(start) >= 0 &&
+                newNode.getStart().compareTo(end) <= 0 &&
+                newNode.getEnd().compareTo(start) >= 0 &&
+                newNode.getEnd().compareTo(end) <= 0);
+    }
+
+    public boolean isItemOverlap(final NodeInterval newNode) {
+        return ((newNode.getStart().compareTo(start) < 0 &&
+                 newNode.getEnd().compareTo(end) >= 0) ||
+                (newNode.getStart().compareTo(start) <= 0 &&
+                 newNode.getEnd().compareTo(end) > 0));
+    }
 
+    @JsonIgnore
     public boolean isRoot() {
         return parent == null;
     }
@@ -236,78 +252,45 @@ public class NodeInterval {
         return end;
     }
 
+    @JsonIgnore
     public NodeInterval getParent() {
         return parent;
     }
 
+    @JsonIgnore
     public NodeInterval getLeftChild() {
         return leftChild;
     }
 
+    @JsonIgnore
     public NodeInterval getRightSibling() {
         return rightSibling;
     }
 
-    public List<Item> getItems() {
-        return items.getItems();
-    }
-
-    public boolean containsItem(final UUID targetId) {
-        return items.containsItem(targetId);
-    }
-
-    // STEPH TODO are parents correctly maintained and/or do we need them?
-    private void addNode(final NodeInterval newNode) {
-        final Item item = newNode.getItems().get(0);
-        if (leftChild == null) {
-            leftChild = newNode;
-            return;
-        }
-
-        NodeInterval prevChild = null;
+    @JsonIgnore
+    public int getNbChildren() {
+        int result = 0;
         NodeInterval curChild = leftChild;
-        do {
-            if (curChild.isItemContained(item)) {
-                curChild.addExistingItem(newNode);
-                return;
-            }
-
-            if (curChild.isItemOverlap(item)) {
-                rebalance(newNode);
-                return;
-            }
-
-            if (item.getStartDate().compareTo(curChild.getStart()) < 0) {
-                newNode.rightSibling = curChild;
-                if (prevChild == null) {
-                    leftChild = newNode;
-                } else {
-                    prevChild.rightSibling = newNode;
-                }
-                return;
-            }
-            prevChild = curChild;
+        while (curChild != null) {
+            result++;
             curChild = curChild.rightSibling;
-        } while (curChild != null);
-
-        prevChild.rightSibling = newNode;
+        }
+        return result;
     }
 
     /**
-     * Since items may be added out of order, there is no guarantee that we don't suddenly had a new node
+     * Since items may be added out of order, there is no guarantee that we don't suddenly have a new node
      * whose interval emcompasses cuurent node(s). In which case we need to rebalance the tree.
      *
      * @param newNode node that triggered a rebalance operation
      */
     private void rebalance(final NodeInterval newNode) {
 
-        final Item item = newNode.getItems().get(0);
-
         NodeInterval prevRebalanced = null;
         NodeInterval curChild = leftChild;
         List<NodeInterval> toBeRebalanced = Lists.newLinkedList();
         do {
-            if (curChild.isItemOverlap(item)) {
+            if (curChild.isItemOverlap(newNode)) {
                 toBeRebalanced.add(curChild);
             } else {
                 if (toBeRebalanced.size() > 0) {
@@ -318,7 +301,10 @@ public class NodeInterval {
             curChild = curChild.rightSibling;
         } while (curChild != null);
 
-        newNode.rightSibling = toBeRebalanced.get(toBeRebalanced.size() - 1).rightSibling;
+        newNode.parent = this;
+        final NodeInterval lastNodeToRebalance = toBeRebalanced.get(toBeRebalanced.size() - 1);
+        newNode.rightSibling = lastNodeToRebalance.rightSibling;
+        lastNodeToRebalance.rightSibling = null;
         if (prevRebalanced == null) {
             leftChild = newNode;
         } else {
@@ -327,6 +313,7 @@ public class NodeInterval {
 
         NodeInterval prev = null;
         for (NodeInterval cur : toBeRebalanced) {
+            cur.parent = newNode;
             if (prev == null) {
                 newNode.leftChild = cur;
             } else {
@@ -344,49 +331,68 @@ public class NodeInterval {
         this.end = (end == null || end.compareTo(newNode.getEnd()) < 0) ? newNode.getEnd() : end;
     }
 
-    private void setAdjustment(final BigDecimal amount, final UUID linkedId) {
-        items.setAdjustment(amount, linkedId);
+    /**
+     * Provides callback for walking the tree.
+     */
+    public interface WalkCallback {
+        public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent);
     }
 
-    private NodeInterval findNode(final LocalDate date, final UUID targetItemId) {
-        Preconditions.checkState(isRoot(), "findNode can only be called from root");
-        return findNodeRecursively2(this, date, targetItemId);
+    /**
+     * Provides custom logic for the search.
+     */
+    public interface SearchCallback {
+        /**
+         * Custom logic to decide which node to return.
+         *
+         * @param curNode found node
+         * @return evaluates whether this is the node that should be returned
+         */
+        boolean isMatch(NodeInterval curNode);
     }
 
-    // TODO That method should be use instaed of findNodeRecursively2 to search the node more effectively using the time
-    // but unfortunately that fails because of our test that use the wrong date when doing adjustments.
-    private NodeInterval findNodeRecursively(final NodeInterval curNode, final LocalDate date, final UUID targetItemId) {
-        if (date.compareTo(curNode.getStart()) < 0 || date.compareTo(curNode.getEnd()) > 0) {
-            return null;
-        }
-        NodeInterval curChild = curNode.getLeftChild();
-        while (curChild != null) {
-            if (curChild.getStart().compareTo(date) <= 0 && curChild.getEnd().compareTo(date) >= 0) {
-                if (curChild.containsItem(targetItemId)) {
-                    return curChild;
-                } else {
-                    return findNodeRecursively(curChild, date, targetItemId);
-                }
-            }
-            curChild = curChild.getRightSibling();
-        }
-        return null;
+    /**
+     * Provides the custom logic for when building resulting state from the tree.
+     */
+    public interface BuildNodeCallback {
+
+        /**
+         * Called when we hit a missing interval where there is no child.
+         *
+         * @param curNode   current node
+         * @param startDate startDate of the new interval to build
+         * @param endDate   endDate of the new interval to build
+         */
+        public void onMissingInterval(NodeInterval curNode, LocalDate startDate, LocalDate endDate);
+
+        /**
+         * Called when we hit a node with no children
+         *
+         * @param curNode current node
+         */
+        public void onLastNode(NodeInterval curNode);
     }
 
-    private NodeInterval findNodeRecursively2(final NodeInterval curNode, final LocalDate date, final UUID targetItemId) {
-
-        if (!curNode.isRoot() && curNode.containsItem(targetItemId)) {
-            return curNode;
-        }
-
-        NodeInterval curChild = curNode.getLeftChild();
-        while (curChild != null) {
-            final NodeInterval result = findNodeRecursively2(curChild, date, targetItemId);
-            if (result != null) {
-                return result;
-            }
-            curChild = curChild.getRightSibling();
-        }
-        return null;
+    /**
+     * Provides the custom logic for when adding nodes in the tree.
+     */
+    public interface AddNodeCallback {
+
+        /**
+         * Called when trying to insert a new node in the tree but there is already
+         * such a node for that same interval.
+         *
+         * @param existingNode
+         * @return this is the return value for the addNode method
+         */
+        public boolean onExistingNode(final NodeInterval existingNode);
+
+        /**
+         * Called prior to insert the new node in the tree
+         *
+         * @param insertionNode the parent node where this new node would be inserted
+         * @return true if addNode should proceed with the insertion and false otherwise
+         */
+        public boolean shouldInsertNode(final NodeInterval insertionNode);
     }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
index 49eb390..8aa8312 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
@@ -28,6 +28,7 @@ import org.joda.time.LocalDate;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.tree.Item.ItemAction;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
@@ -43,7 +44,7 @@ public class SubscriptionItemTree {
     private boolean isBuilt;
 
     private final UUID subscriptionId;
-    private NodeInterval root;
+    private ItemsNodeInterval root;
 
     private List<Item> items;
 
@@ -58,7 +59,8 @@ public class SubscriptionItemTree {
             if (startDateComp != 0) {
                 return startDateComp;
             }
-            int itemTypeComp = Integer.compare(o1.getInvoiceItemType().ordinal(), o2.getInvoiceItemType().ordinal());
+            int itemTypeComp =  (o1.getInvoiceItemType().ordinal()<o2.getInvoiceItemType().ordinal() ? -1 :
+                                 (o1.getInvoiceItemType().ordinal()==o2.getInvoiceItemType().ordinal() ? 0 : 1));
             if (itemTypeComp != 0) {
                 return itemTypeComp;
             }
@@ -70,7 +72,7 @@ public class SubscriptionItemTree {
 
     public SubscriptionItemTree(final UUID subscriptionId) {
         this.subscriptionId = subscriptionId;
-        this.root = new NodeInterval();
+        this.root = new ItemsNodeInterval();
         this.items = new LinkedList<Item>();
         this.existingFixedItems = new LinkedList<InvoiceItem>();
         this.remainingFixedItems = new LinkedList<InvoiceItem>();
@@ -87,12 +89,12 @@ public class SubscriptionItemTree {
             root.addAdjustment(item.getStartDate(), item.getAmount(), item.getLinkedItemId());
         }
         pendingItemAdj.clear();
-        root.build(items, false);
+        root.buildForExistingItems(items);
         isBuilt = true;
     }
 
     /**
-     * Flattens the tree so its depth only has one levl below root -- becomes a list.
+     * Flattens the tree so its depth only has one level below root -- becomes a list.
      * <p>
      * If the tree was not built, it is first built. The list of items is cleared and the state is now reset to unbuilt.
      *
@@ -102,10 +104,10 @@ public class SubscriptionItemTree {
         if (!isBuilt) {
             build();
         }
-        root = new NodeInterval();
+        root = new ItemsNodeInterval();
         for (Item item : items) {
             Preconditions.checkState(item.getAction() == ItemAction.ADD);
-            root.addExistingItem(new NodeInterval(root, new Item(item, reverse ? ItemAction.CANCEL : ItemAction.ADD)));
+            root.addExistingItem(new ItemsNodeInterval(root, new Item(item, reverse ? ItemAction.CANCEL : ItemAction.ADD)));
         }
         items.clear();
         isBuilt = false;
@@ -113,7 +115,7 @@ public class SubscriptionItemTree {
 
     public void buildForMerge() {
         Preconditions.checkState(!isBuilt);
-        root.build(items, true);
+        root.mergeExistingAndProposed(items);
         isBuilt = true;
     }
 
@@ -127,11 +129,11 @@ public class SubscriptionItemTree {
         Preconditions.checkState(!isBuilt);
         switch (invoiceItem.getInvoiceItemType()) {
             case RECURRING:
-                root.addExistingItem(new NodeInterval(root, new Item(invoiceItem, ItemAction.ADD)));
+                root.addExistingItem(new ItemsNodeInterval(root, new Item(invoiceItem, ItemAction.ADD)));
                 break;
 
             case REPAIR_ADJ:
-                root.addExistingItem(new NodeInterval(root, new Item(invoiceItem, ItemAction.CANCEL)));
+                root.addExistingItem(new ItemsNodeInterval(root, new Item(invoiceItem, ItemAction.CANCEL)));
                 break;
 
             case FIXED:
@@ -157,7 +159,7 @@ public class SubscriptionItemTree {
         Preconditions.checkState(!isBuilt);
         switch (invoiceItem.getInvoiceItemType()) {
             case RECURRING:
-                final boolean result = root.mergeProposedItem(new NodeInterval(root, new Item(invoiceItem, ItemAction.ADD)));
+                final boolean result = root.addProposedItem(new ItemsNodeInterval(root, new Item(invoiceItem, ItemAction.ADD)));
                 if (!result) {
                     items.add(new Item(invoiceItem, ItemAction.ADD));
                 }
@@ -272,4 +274,8 @@ public class SubscriptionItemTree {
         return result;
     }
 
+    @VisibleForTesting
+    ItemsNodeInterval getRoot() {
+        return root;
+    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestBillingIntervalDetail.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestBillingIntervalDetail.java
index 65c692f..ce8f84e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestBillingIntervalDetail.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestBillingIntervalDetail.java
@@ -25,6 +25,69 @@ import com.ning.billing.invoice.InvoiceTestSuiteNoDB;
 
 public class TestBillingIntervalDetail extends InvoiceTestSuiteNoDB {
 
+    /*
+     *
+     *         Start         BCD    END_MONTH
+     * |---------|------------|-------|
+     *
+     */
+    @Test(groups = "fast")
+    public void testCalculateFirstBillingCycleDate1() throws Exception {
+        final LocalDate from = new LocalDate("2012-01-16");
+        final int bcd = 17;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL);
+        billingIntervalDetail.calculateFirstBillingCycleDate();
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+    }
+
+    /*
+     *
+     *         Start             END_MONTH    BCD
+     * |---------|-------------------| - - - -|
+     *
+     */
+    @Test(groups = "fast")
+    public void testCalculateFirstBillingCycleDate2() throws Exception {
+        final LocalDate from = new LocalDate("2012-02-16");
+        final int bcd = 30;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL);
+        billingIntervalDetail.calculateFirstBillingCycleDate();
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+    }
+
+    /*
+     * Here the interesting part is that BCD is prior start and
+     *  i) we use MONTHLY billing period
+     * ii) on the next month, there is no such date (2012-02-30 does not exist)
+     *
+     *                                      Start
+     *                              BCD     END_MONTH
+     * |----------------------------|--------|
+     *
+     */
+    @Test(groups = "fast")
+    public void testCalculateFirstBillingCycleDate4() throws Exception {
+        final LocalDate from = new LocalDate("2012-01-31");
+        final int bcd = 30;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.MONTHLY);
+        billingIntervalDetail.calculateFirstBillingCycleDate();
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+    }
+
+    /*
+     *
+     *         BCD                 Start      END_MONTH
+     * |---------|-------------------|-----------|
+     *
+     */
+    @Test(groups = "fast")
+    public void testCalculateFirstBillingCycleDate3() throws Exception {
+        final LocalDate from = new LocalDate("2012-02-16");
+        final int bcd = 14;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL);
+        billingIntervalDetail.calculateFirstBillingCycleDate();
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-02-14"));
+    }
 
     @Test(groups = "fast")
     public void testNextBCDShouldNotBeInThePast() throws Exception {
@@ -102,5 +165,4 @@ public class TestBillingIntervalDetail extends InvoiceTestSuiteNoDB {
         Assert.assertEquals(effectiveEndDate, new LocalDate("2012-05-31"));
     }
 
-
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java
index dd63ced..d286ad0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java
@@ -109,13 +109,15 @@ public class TestInvoiceHelper {
     public static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal TWENTY_FIVE = new BigDecimal("25.0").setScale(KillBillMoney.MAX_SCALE);
 
-    public static final BigDecimal TWENTY_SEVEN = new BigDecimal("27.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal TWENTY_EIGHT = new BigDecimal("28.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal TWENTY_NINE = new BigDecimal("29.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal THIRTY = new BigDecimal("30.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal THIRTY_ONE = new BigDecimal("31.0").setScale(KillBillMoney.MAX_SCALE);
+    public static final BigDecimal THIRTY_THREE = new BigDecimal("33.0").setScale(KillBillMoney.MAX_SCALE);
 
     public static final BigDecimal FORTY = new BigDecimal("40.0").setScale(KillBillMoney.MAX_SCALE);
+    public static final BigDecimal SIXTY_SIX = new BigDecimal("66.0").setScale(KillBillMoney.MAX_SCALE);
+    public static final BigDecimal SEVENTY_FIVE = new BigDecimal("75.0").setScale(KillBillMoney.MAX_SCALE);
 
     public static final BigDecimal EIGHTY_NINE = new BigDecimal("89.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal NINETY = new BigDecimal("90.0").setScale(KillBillMoney.MAX_SCALE);
@@ -124,6 +126,9 @@ public class TestInvoiceHelper {
 
     public static final BigDecimal ONE_HUNDRED = new BigDecimal("100.0").setScale(KillBillMoney.MAX_SCALE);
 
+    public static final BigDecimal THREE_HUNDRED_AND_FOURTY_NINE = new BigDecimal("349.0").setScale(KillBillMoney.MAX_SCALE);
+    public static final BigDecimal THREE_HUNDRED_AND_FIFTY_FOUR = new BigDecimal("354.0").setScale(KillBillMoney.MAX_SCALE);
+
     public static final BigDecimal THREE_HUNDRED_AND_SIXTY_FIVE = new BigDecimal("365.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal THREE_HUNDRED_AND_SIXTY_SIX = new BigDecimal("366.0").setScale(KillBillMoney.MAX_SCALE);
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TestProRation.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TestProRation.java
index 3349840..2bbcf14 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TestProRation.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TestProRation.java
@@ -20,6 +20,7 @@ import static com.ning.billing.invoice.TestInvoiceHelper.*;
 
 import java.math.BigDecimal;
 
+import org.joda.time.Days;
 import org.joda.time.LocalDate;
 import org.testng.annotations.Test;
 
@@ -40,16 +41,18 @@ public class TestProRation extends ProRationInAdvanceTestBase {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 1, 31);
         final LocalDate targetDate = invoiceUtil.buildDate(2011, 2, 24);
 
-        final BigDecimal expectedValue = ONE.add(FIFTEEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, KillBillMoney.ROUNDING_METHOD));
+        // THREE_HUNDRED_AND_FOURTY_NINE is number of days between startDate and expected first billing cycle date (2012, 1, 15);
+        final BigDecimal expectedValue = THREE_HUNDRED_AND_FOURTY_NINE.divide(THREE_HUNDRED_AND_SIXTY_FIVE, KillBillMoney.ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, expectedValue);
     }
 
     @Test(groups = "fast")
     public void testSinglePlan_PrecedingProRation_CrossingYearBoundary() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2010, 12, 15);
-        final LocalDate targetDate = invoiceUtil.buildDate(2011, 1, 13);
+        final LocalDate targetDate = invoiceUtil.buildDate(2012, 1, 13);
 
-        final BigDecimal expectedValue = ONE.add(TWENTY.divide(THREE_HUNDRED_AND_SIXTY_FIVE, KillBillMoney.ROUNDING_METHOD));
+        // THREE_HUNDRED_AND_FOURTY_NINE is number of days between startDate and expected first billing cycle date (2011, 12, 4);
+        final BigDecimal expectedValue = ONE.add(THREE_HUNDRED_AND_FIFTY_FOUR.divide(THREE_HUNDRED_AND_SIXTY_FIVE, KillBillMoney.ROUNDING_METHOD));
         testCalculateNumberOfBillingCycles(startDate, targetDate, 4, expectedValue);
     }
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TestProRation.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TestProRation.java
index 1e52e8b..fc8b0bd 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TestProRation.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TestProRation.java
@@ -20,6 +20,7 @@ import static com.ning.billing.invoice.TestInvoiceHelper.*;
 
 import java.math.BigDecimal;
 
+import org.joda.time.Days;
 import org.joda.time.LocalDate;
 import org.testng.annotations.Test;
 
@@ -45,35 +46,23 @@ public class TestProRation extends ProRationInAdvanceTestBase {
         expectedValue = FOURTEEN.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 10, expectedValue);
 
-        expectedValue = FOURTEEN.divide(NINETY, KillBillMoney.ROUNDING_METHOD);
+        // 75 is number of days between phaseChangeDate and next billing cycle date (2011, 5, 10)
+        // 89 is total number of days between the next and previous billing period  (2011, 2, 10) -> (2011, 5, 10)
+        expectedValue = SEVENTY_FIVE.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 10, expectedValue);
     }
 
     @Test(groups = "fast")
-    public void testSinglePlan_WithPhaseChange_BeforeBillCycleDay() throws InvalidDateSequenceException {
-        final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 3);
-        final LocalDate phaseChangeDate = invoiceUtil.buildDate(2011, 2, 17);
-        final LocalDate targetDate = invoiceUtil.buildDate(2011, 3, 1);
-
-        BigDecimal expectedValue;
-        expectedValue = FOURTEEN.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD);
-        testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue);
-
-        expectedValue = FOURTEEN.divide(NINETY, KillBillMoney.ROUNDING_METHOD);
-        testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue);
-    }
-
-    @Test(groups = "fast")
     public void testSinglePlan_WithPhaseChange_OnBillCycleDay() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 3);
         final LocalDate phaseChangeDate = invoiceUtil.buildDate(2011, 2, 17);
-        final LocalDate targetDate = invoiceUtil.buildDate(2011, 3, 3);
+        final LocalDate targetDate = invoiceUtil.buildDate(2011, 5, 3);
 
         BigDecimal expectedValue;
         expectedValue = FOURTEEN.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue);
 
-        expectedValue = FOURTEEN.divide(NINETY, KillBillMoney.ROUNDING_METHOD).add(ONE);
+        expectedValue = ONE.add(SEVENTY_FIVE.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD));
         testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue);
     }
 
@@ -81,13 +70,13 @@ public class TestProRation extends ProRationInAdvanceTestBase {
     public void testSinglePlan_WithPhaseChange_AfterBillCycleDay() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 3);
         final LocalDate phaseChangeDate = invoiceUtil.buildDate(2011, 2, 17);
-        final LocalDate targetDate = invoiceUtil.buildDate(2011, 3, 4);
+        final LocalDate targetDate = invoiceUtil.buildDate(2011, 5, 4);
 
         BigDecimal expectedValue;
         expectedValue = FOURTEEN.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue);
 
-        expectedValue = FOURTEEN.divide(NINETY, KillBillMoney.ROUNDING_METHOD).add(ONE);
+        expectedValue = SEVENTY_FIVE.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD).add(ONE);
         testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue);
     }
 
@@ -236,9 +225,10 @@ public class TestProRation extends ProRationInAdvanceTestBase {
         final LocalDate targetDate = invoiceUtil.buildDate(2011, 5, 21);
 
         BigDecimal expectedValue;
-        expectedValue = SEVEN.divide(NINETY_TWO, KillBillMoney.ROUNDING_METHOD);
-        expectedValue = expectedValue.add(ONE);
-        expectedValue = expectedValue.add(THREE.divide(NINETY_TWO, KillBillMoney.ROUNDING_METHOD));
+        // startDate, 2011, 4, 7 -> 66 days out of 2011, 1, 7, 2011, 4, 7 -> 90
+        expectedValue = SIXTY_SIX.divide(NINETY, KillBillMoney.ROUNDING_METHOD);
+        // 2011, 1, 7, planChangeDate-> 33 days out of 2011, 4, 7, 2011, 7, 7 -> 89
+        expectedValue = expectedValue.add(THIRTY_THREE.divide(NINETY_ONE, KillBillMoney.ROUNDING_METHOD));
         testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue);
 
         expectedValue = FIVE.divide(EIGHTY_NINE, KillBillMoney.ROUNDING_METHOD).add(ONE);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
index 87c1bc9..476af4a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
@@ -56,7 +56,7 @@ public abstract class ProRationTestBase extends InvoiceTestSuiteNoDB {
             final BigDecimal numberOfBillingCycles;
             numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay);
 
-            assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0);
+            assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0, "Actual: " + numberOfBillingCycles.toString() + "; expected: " + expectedValue.toString());
         } catch (InvalidDateSequenceException idse) {
             throw idse;
         } catch (Exception e) {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
new file mode 100644
index 0000000..9512591
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2010-2014 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.tree;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.annotations.Test;
+
+import com.ning.billing.invoice.tree.NodeInterval.AddNodeCallback;
+import com.ning.billing.invoice.tree.NodeInterval.BuildNodeCallback;
+import com.ning.billing.invoice.tree.NodeInterval.SearchCallback;
+import com.ning.billing.invoice.tree.NodeInterval.WalkCallback;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestNodeInterval /* extends InvoiceTestSuiteNoDB  */ {
+
+    private AddNodeCallback CALLBACK = new DummyAddNodeCallback();
+
+    public class DummyNodeInterval extends NodeInterval {
+
+        private final UUID id;
+
+        public DummyNodeInterval() {
+            this.id = UUID.randomUUID();
+        }
+
+        public DummyNodeInterval(final NodeInterval parent, final LocalDate startDate, final LocalDate endDate) {
+            super(parent, startDate, endDate);
+            this.id = UUID.randomUUID();
+        }
+
+        public UUID getId() {
+            return id;
+        }
+    }
+
+    public class DummyAddNodeCallback implements AddNodeCallback {
+
+        @Override
+        public boolean onExistingNode(final NodeInterval existingNode) {
+            return false;
+        }
+
+        @Override
+        public boolean shouldInsertNode(final NodeInterval insertionNode) {
+            return true;
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testAddExistingItemSimple() {
+        final DummyNodeInterval root = new DummyNodeInterval();
+
+        final DummyNodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addNode(top, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final DummyNodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final DummyNodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addNode(firstChildLevel1, CALLBACK);
+        root.addNode(secondChildLevel1, CALLBACK);
+        root.addNode(thirdChildLevel1, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final DummyNodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-5");
+        final DummyNodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addNode(firstChildLevel2, CALLBACK);
+        root.addNode(secondChildLevel2, CALLBACK);
+        root.addNode(thirdChildLevel2, CALLBACK);
+
+        checkNode(top, 3, root, firstChildLevel1, null);
+        checkNode(firstChildLevel1, 2, top, firstChildLevel2, secondChildLevel1);
+        checkNode(secondChildLevel1, 0, top, null, thirdChildLevel1);
+        checkNode(thirdChildLevel1, 1, top, thirdChildLevel2, null);
+
+        checkNode(firstChildLevel2, 0, firstChildLevel1, null, secondChildLevel2);
+        checkNode(secondChildLevel2, 0, firstChildLevel1, null, null);
+        checkNode(thirdChildLevel2, 0, thirdChildLevel1, null, null);
+    }
+
+    @Test(groups = "fast")
+    public void testAddExistingItemWithRebalance() {
+        final DummyNodeInterval root = new DummyNodeInterval();
+
+        final DummyNodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addNode(top, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final DummyNodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-5");
+        final DummyNodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addNode(firstChildLevel2, CALLBACK);
+        root.addNode(secondChildLevel2, CALLBACK);
+        root.addNode(thirdChildLevel2, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final DummyNodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final DummyNodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addNode(firstChildLevel1, CALLBACK);
+        root.addNode(secondChildLevel1, CALLBACK);
+        root.addNode(thirdChildLevel1, CALLBACK);
+
+        checkNode(top, 3, root, firstChildLevel1, null);
+        checkNode(firstChildLevel1, 2, top, firstChildLevel2, secondChildLevel1);
+        checkNode(secondChildLevel1, 0, top, null, thirdChildLevel1);
+        checkNode(thirdChildLevel1, 1, top, thirdChildLevel2, null);
+
+        checkNode(firstChildLevel2, 0, firstChildLevel1, null, secondChildLevel2);
+        checkNode(secondChildLevel2, 0, firstChildLevel1, null, null);
+        checkNode(thirdChildLevel2, 0, thirdChildLevel1, null, null);
+    }
+
+    @Test(groups = "fast")
+    public void testBuild() {
+        final DummyNodeInterval root = new DummyNodeInterval();
+
+        final DummyNodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addNode(top, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final DummyNodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final DummyNodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addNode(firstChildLevel1, CALLBACK);
+        root.addNode(secondChildLevel1, CALLBACK);
+        root.addNode(thirdChildLevel1, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final DummyNodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-5");
+        final DummyNodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addNode(firstChildLevel2, CALLBACK);
+        root.addNode(secondChildLevel2, CALLBACK);
+        root.addNode(thirdChildLevel2, CALLBACK);
+
+        final List<NodeInterval> output = new LinkedList<NodeInterval>();
+
+        // Just build the missing pieces.
+        root.build(new BuildNodeCallback() {
+            @Override
+            public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+                output.add(createNodeInterval(startDate, endDate));
+            }
+
+            @Override
+            public void onLastNode(final NodeInterval curNode) {
+                // Nothing
+            }
+        });
+
+        final List<NodeInterval> expected = new LinkedList<NodeInterval>();
+        expected.add(createNodeInterval("2014-01-05", "2014-01-07"));
+        expected.add(createNodeInterval("2014-01-07", "2014-01-08"));
+        expected.add(createNodeInterval("2014-01-15", "2014-01-16"));
+        expected.add(createNodeInterval("2014-01-17", "2014-02-01"));
+
+        assertEquals(output.size(), expected.size());
+        checkInterval(output.get(0), expected.get(0));
+        checkInterval(output.get(1), expected.get(1));
+    }
+
+    @Test(groups = "fast")
+    public void testSearch() {
+        final DummyNodeInterval root = new DummyNodeInterval();
+
+        final DummyNodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addNode(top, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final DummyNodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final DummyNodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addNode(firstChildLevel1, CALLBACK);
+        root.addNode(secondChildLevel1, CALLBACK);
+        root.addNode(thirdChildLevel1, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final DummyNodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-5");
+        final DummyNodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addNode(firstChildLevel2, CALLBACK);
+        root.addNode(secondChildLevel2, CALLBACK);
+        root.addNode(thirdChildLevel2, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel3 = createNodeInterval("2014-01-01", "2014-01-02");
+        final DummyNodeInterval secondChildLevel3 = createNodeInterval("2014-01-03", "2014-01-04");
+        root.addNode(firstChildLevel3, CALLBACK);
+        root.addNode(secondChildLevel3, CALLBACK);
+
+        final NodeInterval search1 = root.findNode(new LocalDate("2014-01-04"), new SearchCallback() {
+            @Override
+            public boolean isMatch(final NodeInterval curNode) {
+                return ((DummyNodeInterval) curNode).getId().equals(secondChildLevel3.getId());
+            }
+        });
+        checkInterval(search1, secondChildLevel3);
+
+        final NodeInterval search2 = root.findNode(new SearchCallback() {
+            @Override
+            public boolean isMatch(final NodeInterval curNode) {
+                return ((DummyNodeInterval) curNode).getId().equals(thirdChildLevel2.getId());
+            }
+        });
+        checkInterval(search2, thirdChildLevel2);
+
+        final NodeInterval nullSearch = root.findNode(new SearchCallback() {
+            @Override
+            public boolean isMatch(final NodeInterval curNode) {
+                return ((DummyNodeInterval) curNode).getId().equals("foo");
+            }
+        });
+        assertNull(nullSearch);
+    }
+
+    @Test(groups = "fast")
+    public void testWalkTree() {
+        final DummyNodeInterval root = new DummyNodeInterval();
+
+        final DummyNodeInterval firstChildLevel0 = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addNode(firstChildLevel0, CALLBACK);
+
+        final DummyNodeInterval secondChildLevel0 = createNodeInterval("2014-02-01", "2014-03-01");
+        root.addNode(secondChildLevel0, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final DummyNodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final DummyNodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addNode(firstChildLevel1, CALLBACK);
+        root.addNode(secondChildLevel1, CALLBACK);
+        root.addNode(thirdChildLevel1, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final DummyNodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-05");
+        final DummyNodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addNode(firstChildLevel2, CALLBACK);
+        root.addNode(secondChildLevel2, CALLBACK);
+        root.addNode(thirdChildLevel2, CALLBACK);
+
+        final DummyNodeInterval firstChildLevel3 = createNodeInterval("2014-01-01", "2014-01-02");
+        final DummyNodeInterval secondChildLevel3 = createNodeInterval("2014-01-03", "2014-01-04");
+        root.addNode(firstChildLevel3, CALLBACK);
+        root.addNode(secondChildLevel3, CALLBACK);
+
+        final List<NodeInterval> expected = new LinkedList<NodeInterval>();
+        expected.add(root);
+        expected.add(firstChildLevel0);
+        expected.add(firstChildLevel1);
+        expected.add(firstChildLevel2);
+        expected.add(firstChildLevel3);
+        expected.add(secondChildLevel2);
+        expected.add(secondChildLevel3);
+        expected.add(secondChildLevel1);
+        expected.add(thirdChildLevel1);
+        expected.add(thirdChildLevel2);
+        expected.add(secondChildLevel0);
+
+        final List<NodeInterval> result = new LinkedList<NodeInterval>();
+        root.walkTree(new WalkCallback() {
+            @Override
+            public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
+                result.add(curNode);
+            }
+        });
+
+        assertEquals(result.size(), expected.size());
+        for (int i = 0; i < result.size(); i++) {
+            if (i == 0) {
+                assertTrue(result.get(0).isRoot());
+                checkInterval(result.get(0), createNodeInterval("2014-01-01", "2014-03-01"));
+            } else {
+                checkInterval(result.get(i), expected.get(i));
+            }
+        }
+    }
+
+    private void checkInterval(final NodeInterval real, final NodeInterval expected) {
+        assertEquals(real.getStart(), expected.getStart());
+        assertEquals(real.getEnd(), expected.getEnd());
+    }
+
+    private void checkNode(final NodeInterval node, final int expectedChildren, final DummyNodeInterval expectedParent, final DummyNodeInterval expectedLeftChild, final DummyNodeInterval expectedRightSibling) {
+        assertEquals(node.getNbChildren(), expectedChildren);
+        assertEquals(node.getParent(), expectedParent);
+        assertEquals(node.getRightSibling(), expectedRightSibling);
+        assertEquals(node.getLeftChild(), expectedLeftChild);
+        assertEquals(node.getLeftChild(), expectedLeftChild);
+    }
+
+    private DummyNodeInterval createNodeInterval(final LocalDate startDate, final LocalDate endDate) {
+        return new DummyNodeInterval(null, startDate, endDate);
+    }
+
+    private DummyNodeInterval createNodeInterval(final String startDate, final String endDate) {
+        return createNodeInterval(new LocalDate(startDate), new LocalDate(endDate));
+    }
+
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
index f157cdf..258ada8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -16,11 +16,15 @@
 
 package com.ning.billing.invoice.tree;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.api.Currency;
@@ -29,6 +33,7 @@ import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
+import com.ning.billing.util.jackson.ObjectMapper;
 
 import com.google.common.collect.Lists;
 
@@ -76,7 +81,6 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         tree.addItem(repair);
         tree.build();
         verifyResult(tree.getView(), expectedResult);
-
         tree = new SubscriptionItemTree(subscriptionId);
         tree.addItem(repair);
         tree.addItem(newItem);
@@ -500,6 +504,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         verifyResult(tree.getView(), expectedResult);
     }
 
+
     @Test(groups = "fast")
     public void testMergeCancellationWithTwoMiddleRepair() {
 
@@ -533,6 +538,26 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         expectedResult.add(repair1);
         expectedResult.add(repair2);
         verifyResult(tree.getView(), expectedResult);
+
+
+        // Dot it again but with proposed items out of order
+        final SubscriptionItemTree treeAgain = new SubscriptionItemTree(subscriptionId);
+        final InvoiceItem monthlyAgain = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+        treeAgain.addItem(monthlyAgain);
+        treeAgain.flatten(true);
+
+        final InvoiceItem proposed2Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate1, blockDate2, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed1Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate1, monthlyAmount, monthlyRate, currency);
+        final InvoiceItem proposed3Again = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate2, endDate, monthlyAmount, monthlyRate, currency);
+
+        treeAgain.mergeProposedItem(proposed1Again);
+        treeAgain.mergeProposedItem(proposed2Again);
+        treeAgain.mergeProposedItem(proposed3Again);
+        treeAgain.buildForMerge();
+
+        verifyResult(treeAgain.getView(), expectedResult);
+
+
     }
 
     @Test(groups = "fast")
@@ -761,6 +786,38 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         verifyResult(tree.getView(), expectedResult);
     }
 
+    @Test(groups = "fast")
+    public void verifyJson() {
+
+        SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+        final UUID id1 = UUID.fromString("e8ba6ce7-9bd4-417d-af53-70951ecaa99f");
+        final InvoiceItem yearly1 = new RecurringInvoiceItem(id1, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate("2014-01-01"), new LocalDate("2015-01-01"), BigDecimal.TEN, BigDecimal.TEN, currency);
+        tree.addItem(yearly1);
+
+        final UUID id2 = UUID.fromString("48db1317-9a6e-4666-bcc5-fc7d3d0defc8");
+        final InvoiceItem newItem = new RecurringInvoiceItem(id2, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, "other-plan", "other-plan", new LocalDate("2014-08-01"), new LocalDate("2015-01-01"), BigDecimal.ONE, BigDecimal.ONE, currency);
+        tree.addItem(newItem);
+
+        final UUID id3 = UUID.fromString("02ec57f5-2723-478b-86ba-ebeaedacb9db");
+        final InvoiceItem repair = new RepairAdjInvoiceItem(id3, new DateTime(), invoiceId, accountId, new LocalDate("2014-08-01"), new LocalDate("2015-01-01"), BigDecimal.TEN.negate(), currency, yearly1.getId());
+        tree.addItem(repair);
+
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            tree.getRoot().jsonSerializeTree(new ObjectMapper(), outputStream);
+
+            final String json = outputStream.toString("UTF-8");
+            final String expectedJson = "[{\"start\":\"2014-01-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"startDate\":\"2014-01-01\",\"endDate\":\"2015-01-01\",\"amount\":10,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"}]},[{\"start\":\"2014-08-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"48db1317-9a6e-4666-bcc5-fc7d3d0defc8\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":1,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"},{\"id\":\"02ec57f5-2723-478b-86ba-ebeaedacb9db\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":10,\"currency\":\"USD\",\"linkedId\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"action\":\"CANCEL\"}]}]]";
+
+            assertEquals(json, expectedJson);
+
+        } catch (IOException e) {
+            e.printStackTrace();
+            Assert.fail(e.getMessage());
+        }
+
+    }
+
     private void verifyResult(final List<InvoiceItem> result, final List<InvoiceItem> expectedResult) {
         assertEquals(result.size(), expectedResult.size());
         for (int i = 0; i < expectedResult.size(); i++) {