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) {