killbill-memoizeit

Invoice fix to address issues related with leading pro-rations. In

2/28/2014 10:42:45 PM

Details

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..f8abb84
--- /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.27")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-161.27")));
+        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.71")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("-161.27")));
+        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.58")),
+                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.71")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2327.71")));
+        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.27")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2012, 6, 4), InvoiceItemType.CBA_ADJ, new BigDecimal("-2327.71")));
+        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.71")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2327.71")));
+        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.27")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 4), new LocalDate(2012, 6, 4), InvoiceItemType.CBA_ADJ, new BigDecimal("-2327.71")));
+        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 c99bca8..a8b442b 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.76")),
-                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-137.76")));
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2334.19")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-2334.19")));
         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..9c7de27 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
@@ -22,9 +22,6 @@ import com.ning.billing.catalog.api.BillingPeriod;
 
 public class BillingIntervalDetail {
 
-
-    private final boolean shouldUsePatch = true;
-
     private final LocalDate startDate;
     private final LocalDate endDate;
     private final LocalDate targetDate;
@@ -78,11 +75,14 @@ public class BillingIntervalDetail {
             billingCycleDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), billingCycleDay, startDate.getChronology());
         }
 
+        int numberOfPeriods = 0;
+        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         LocalDate proposedDate = billingCycleDate;
         while (proposedDate.isBefore(startDate)) {
-            proposedDate = proposedDate.plusMonths(1);
+            proposedDate = proposedDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            numberOfPeriods += 1;
         }
-        firstBillingCycleDate = proposedDate;
+        firstBillingCycleDate = alignProposedBillCycleDate(proposedDate);
     }
 
     private void calculateEffectiveEndDate() {
@@ -144,9 +144,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/test/java/com/ning/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java
index 17c2f2c..e2f53c6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/TestInvoiceHelper.java
@@ -112,7 +112,6 @@ public class TestInvoiceHelper {
     public static final BigDecimal TWENTY_FOUR = new BigDecimal("24.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal TWENTY_FIVE = new BigDecimal("25.0").setScale(NUMBER_OF_DECIMALS);
 
-    public static final BigDecimal TWENTY_SEVEN = new BigDecimal("27.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal TWENTY_EIGHT = new BigDecimal("28.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal TWENTY_NINE = new BigDecimal("29.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal THIRTY = new BigDecimal("30.0").setScale(NUMBER_OF_DECIMALS);
@@ -120,6 +119,8 @@ public class TestInvoiceHelper {
 
     public static final BigDecimal FORTY = new BigDecimal("40.0").setScale(NUMBER_OF_DECIMALS);
 
+    public static final BigDecimal SEVENTY_FIVE = new BigDecimal("75.0").setScale(NUMBER_OF_DECIMALS);
+
     public static final BigDecimal EIGHTY_NINE = new BigDecimal("89.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal NINETY = new BigDecimal("90.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal NINETY_ONE = new BigDecimal("91.0").setScale(NUMBER_OF_DECIMALS);
@@ -127,6 +128,9 @@ public class TestInvoiceHelper {
 
     public static final BigDecimal ONE_HUNDRED = new BigDecimal("100.0").setScale(NUMBER_OF_DECIMALS);
 
+    public static final BigDecimal THREE_HUNDRED_AND_FOURTY_NINE = new BigDecimal("349.0").setScale(NUMBER_OF_DECIMALS);
+    public static final BigDecimal THREE_HUNDRED_AND_FIFTY_FOUR = new BigDecimal("354.0").setScale(NUMBER_OF_DECIMALS);
+
     public static final BigDecimal THREE_HUNDRED_AND_SIXTY_FIVE = new BigDecimal("365.0").setScale(NUMBER_OF_DECIMALS);
     public static final BigDecimal THREE_HUNDRED_AND_SIXTY_SIX = new BigDecimal("366.0").setScale(NUMBER_OF_DECIMALS);
 
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 dde0beb..1fb7106 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;
 
@@ -39,16 +40,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, NUMBER_OF_DECIMALS, 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, NUMBER_OF_DECIMALS, 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, NUMBER_OF_DECIMALS, 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, NUMBER_OF_DECIMALS, 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 ce3608e..ada9ec9 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;
 
@@ -44,35 +45,23 @@ public class TestProRation extends ProRationInAdvanceTestBase {
         expectedValue = FOURTEEN.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 10, expectedValue);
 
-        expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, 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, NUMBER_OF_DECIMALS, 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, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
-        testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue);
-
-        expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, 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, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue);
 
-        expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE);
+        expectedValue = ONE.add(SEVENTY_FIVE.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
         testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue);
     }
 
@@ -80,13 +69,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, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, phaseChangeDate, targetDate, 3, expectedValue);
 
-        expectedValue = FOURTEEN.divide(NINETY, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE);
+        expectedValue = SEVENTY_FIVE.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE);
         testCalculateNumberOfBillingCycles(phaseChangeDate, targetDate, 3, expectedValue);
     }
 
@@ -235,12 +224,13 @@ public class TestProRation extends ProRationInAdvanceTestBase {
         final LocalDate targetDate = invoiceUtil.buildDate(2011, 5, 21);
 
         BigDecimal expectedValue;
-        expectedValue = SEVEN.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
-        expectedValue = expectedValue.add(ONE);
-        expectedValue = expectedValue.add(THREE.divide(NINETY_TWO, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
+        // startDate, 2011, 4, 7 -> 66 days out of 2011, 1, 7, 2011, 4, 7 -> 90
+        expectedValue = new BigDecimal("66.00").divide(NINETY, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+        // 2011, 1, 7, planChangeDate-> 33 days out of 2011, 4, 7, 2011, 7, 7 -> 89
+        expectedValue = expectedValue.add(new BigDecimal("33.00").divide(NINETY_ONE, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD)).setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(startDate, planChangeDate, targetDate, 7, expectedValue);
 
-        expectedValue = FIVE.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE);
+        expectedValue = FIVE.divide(EIGHTY_NINE, NUMBER_OF_DECIMALS, ROUNDING_METHOD).add(ONE).setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
         testCalculateNumberOfBillingCycles(planChangeDate, targetDate, 15, expectedValue);
     }
 
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 f34b1e9..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
@@ -807,8 +807,6 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
             tree.getRoot().jsonSerializeTree(new ObjectMapper(), outputStream);
 
             final String json = outputStream.toString("UTF-8");
-            System.out.println(json);
-
             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);