killbill-memoizeit

invoice: Fixes #598

8/25/2016 8:38:16 PM

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
index 7e1b7b3..4635782 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
@@ -49,6 +49,7 @@ import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
 import org.killbill.billing.payment.api.PluginProperty;
@@ -223,7 +224,54 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
         invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
         assertEquals(invoices.size(), 3);
         assertEquals(invoices.get(2).getChargedAmount().compareTo(new BigDecimal("9.00")), 0); // 10 (recurring) - 1 (repair)
+    }
+
+
+
+    // Use custom plan definition to create a THIRTY_DAYS plan with no trial and test issue #598
+    @Test(groups = "slow")
+    public void testWithThirtyDaysPlan() throws Exception {
+
+        // Create a per-tenant catalog with one plan
+        final SimplePlanDescriptor desc1 = new DefaultSimplePlanDescriptor("thirty-monthly", "Thirty", ProductCategory.BASE, account.getCurrency(), BigDecimal.TEN, BillingPeriod.THIRTY_DAYS, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of());
+        catalogUserApi.addSimplePlan(desc1, init, testCallContext);
+        StaticCatalog catalog = catalogUserApi.getCurrentCatalog("dummy", testCallContext);
+        assertEquals(catalog.getCurrentPlans().length, 1);
+
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("thirty-monthly", null);
 
+        createEntitlement(spec, null, true);
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+        assertEquals(invoices.size(), 1);
+        assertEquals(invoices.get(0).getChargedAmount().compareTo(BigDecimal.TEN), 0);
+        assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 1), new LocalDate(2016, 7, 1), InvoiceItemType.RECURRING, BigDecimal.TEN));
+        invoiceChecker.checkInvoiceNoAudits(invoices.get(0), callContext, expectedInvoices);
+
+        int invoiceSize = 2;
+        LocalDate startDate = new LocalDate(2016, 7, 1);
+        for (int i = 0; i < 14; i++) {
+
+            expectedInvoices.clear();
+
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+            clock.addDays(30);
+            assertListenerStatus();
+
+            LocalDate endDate = startDate.plusDays(30);
+            invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+            assertEquals(invoices.size(), invoiceSize);
+
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDate, endDate, InvoiceItemType.RECURRING, BigDecimal.TEN));
+            invoiceChecker.checkInvoiceNoAudits(invoices.get(invoices.size() - 1), callContext, expectedInvoices);
+
+            startDate = endDate;
+            invoiceSize++;
+        }
     }
 
     private Entitlement createEntitlement(final String planName, final boolean expectPayment) throws EntitlementApiException {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
index 846b057..8a75ef0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
@@ -32,12 +32,14 @@ public class BillingIntervalDetail {
     private final int billingCycleDay;
     private final BillingPeriod billingPeriod;
     private final BillingMode billingMode;
+    private final boolean isMonthBased;
     // First date after the startDate aligned with the BCD
     private LocalDate firstBillingCycleDate;
     // Date up to which we should bill
     private LocalDate effectiveEndDate;
     private LocalDate lastBillingCycleDate;
 
+
     public BillingIntervalDetail(final LocalDate startDate,
                                  final LocalDate endDate,
                                  final LocalDate targetDate,
@@ -54,6 +56,7 @@ public class BillingIntervalDetail {
         }
         this.billingPeriod = billingPeriod;
         this.billingMode = billingMode;
+        this.isMonthBased = (billingPeriod.getPeriod().getMonths() | billingPeriod.getPeriod().getYears()) > 0;
         computeAll();
     }
 
@@ -67,7 +70,7 @@ public class BillingIntervalDetail {
 
     public LocalDate getFutureBillingDateFor(final int nbPeriod) {
         final LocalDate proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, nbPeriod);
-        return alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        return alignProposedBillCycleDate(proposedDate, billingCycleDay, isMonthBased);
     }
 
     public LocalDate getLastBillingCycleDate() {
@@ -76,7 +79,7 @@ public class BillingIntervalDetail {
 
     public LocalDate getNextBillingCycleDate() {
         final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plus(billingPeriod.getPeriod()) : firstBillingCycleDate;
-        final LocalDate nextBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        final LocalDate nextBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay, isMonthBased);
         return nextBillingCycleDate;
     }
 
@@ -106,7 +109,7 @@ public class BillingIntervalDetail {
         while (proposedDate.isBefore(startDate)) {
             proposedDate = proposedDate.plus(billingPeriod.getPeriod());
         }
-        firstBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        firstBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay, isMonthBased);
     }
 
     private void calculateEffectiveEndDate() {
@@ -138,7 +141,7 @@ public class BillingIntervalDetail {
             nextProposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
-        proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay, isMonthBased);
 
         // We honor the endDate as long as it does not go beyond our targetDate (by construction this cannot be after the nextProposedDate neither.
         if (endDate != null && !endDate.isAfter(targetDate)) {
@@ -168,7 +171,7 @@ public class BillingIntervalDetail {
             proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
-        proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay, isMonthBased);
 
         // The proposedDate is greater to our endDate => return it
         if (endDate != null && endDate.isBefore(proposedDate)) {
@@ -196,7 +199,7 @@ public class BillingIntervalDetail {
 
         // Our proposed date is billingCycleDate prior to the effectiveEndDate
         proposedDate = proposedDate.minus(billingPeriod.getPeriod());
-        proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay, isMonthBased);
 
         if (proposedDate.isBefore(firstBillingCycleDate)) {
             // Make sure not to go too far in the past
@@ -209,7 +212,11 @@ public class BillingIntervalDetail {
     //
     // We start from a billCycleDate
     //
-    public static LocalDate alignProposedBillCycleDate(final LocalDate proposedDate, final int billingCycleDay) {
+    private static LocalDate alignProposedBillCycleDate(final LocalDate proposedDate, final int billingCycleDay, final boolean isMonthBased) {
+        // billingCycleDay alignment only makes sense for month based BillingPeriod (MONTHLY, QUARTERLY, BIANNUAL, ANNUAL)
+        if (!isMonthBased) {
+            return proposedDate;
+        }
         final int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
         int proposedBillCycleDate = proposedDate.getDayOfMonth();
         if (proposedBillCycleDate < billingCycleDay && billingCycleDay <= lastDayOfMonth) {