diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index fd03318..fa7cc6c 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -32,6 +32,7 @@ import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.beatrix.util.PaymentChecker.ExpectedPaymentCheck;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PriceListSet;
@@ -609,6 +610,106 @@ public class TestIntegration extends TestIntegrationBase {
checkNoMoreInvoiceToGenerate(account);
}
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/715")
+ public void testWithPauseResumeAfterENT_CANCELLEDBlockingState() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
+ final int billingDay = 2;
+ // set clock to the initial start date
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+ final UUID accountId = account.getId();
+ assertNotNull(account);
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
+ //
+ final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ assertNotNull(baseEntitlement);
+
+ //
+ // VERIFY CTD HAS BEEN SET
+ //
+ final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) baseEntitlement.getSubscriptionBase();
+ final DateTime startDate = subscription.getCurrentPhaseStart();
+ final BigDecimal rate = subscription.getCurrentPhase().getFixed().getPrice().getPrice(Currency.USD);
+ verifyTestResult(accountId, subscription.getId(), startDate, null, rate, clock.getUTCNow(), 1);
+
+ //
+ // MOVE TIME TO AFTER TRIAL (2012-03-04)
+ //
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(accountId,
+ 1,
+ callContext,
+ ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 2, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO)));
+
+ invoiceChecker.checkInvoice(accountId,
+ 2,
+ callContext,
+ ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 4, 2), InvoiceItemType.RECURRING, new BigDecimal("249.95"))));
+
+ // Pause the entitlement between 2012-03-05 and 2012-03-15
+ DefaultEntitlement entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+ entitlementApi.pause(entitlement.getBundleId(), new LocalDate(2012, 3, 5), ImmutableList.<PluginProperty>of(), callContext);
+ entitlementApi.resume(entitlement.getBundleId(), new LocalDate(2012, 3, 15), ImmutableList.<PluginProperty>of(), callContext);
+
+ // Advance clock to 2012-03-07
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.INVOICE);
+ clock.addDays(3);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(accountId,
+ 2,
+ callContext,
+ ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 4, 2), InvoiceItemType.RECURRING, new BigDecimal("249.95"))));
+
+ invoiceChecker.checkInvoice(accountId,
+ 3,
+ callContext,
+ ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 5), new LocalDate(2012, 4, 2), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-225.76")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 7), new LocalDate(2012, 3, 7), InvoiceItemType.CBA_ADJ, new BigDecimal("225.76"))));
+
+ // Entitlement should be blocked
+ entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+ Assert.assertEquals(entitlement.getState(), EntitlementState.BLOCKED);
+
+ // Advance clock to 2012-03-12, nothing should happen
+ clock.addDays(5);
+ assertListenerStatus();
+
+ // Entitlement is still blocked
+ entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
+ Assert.assertEquals(entitlement.getState(), EntitlementState.BLOCKED);
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ assertEquals(invoices.size(), 3);
+
+ // Cancel entitlement start of term but with billing policy immediate (ENT_BLOCKED must be after ENT_CANCELLED to trigger the bug)
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.NULL_INVOICE);
+ baseEntitlement.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(2012, 3, 2), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ // 2012-03-16
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ clock.addDays(4);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+ clock.addMonths(3);
+ assertListenerStatus();
+
+ // No new invoices
+ invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ assertEquals(invoices.size(), 3);
+ }
+
@Test(groups = "slow")
public void testForMultipleRecurringPhases() throws Exception {
final DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
index a03af67..cf7507e 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/BlockingCalculator.java
@@ -131,7 +131,7 @@ public class BlockingCalculator {
}
final List<BlockingState> subscriptionBlockingEvents = perSubscriptionBlockingEvents.get(subscription.getId()) != null ? perSubscriptionBlockingEvents.get(subscription.getId()) : ImmutableList.<BlockingState>of();
- final List<BlockingState> aggregateSubscriptionBlockingEvents = getAggregateBlockingEventsPerSubscription(subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
+ final List<BlockingState> aggregateSubscriptionBlockingEvents = getAggregateBlockingEventsPerSubscription(subscription.getEndDate(), subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
final List<DisabledDuration> accountBlockingDurations = createBlockingDurations(aggregateSubscriptionBlockingEvents);
billingEventsToAdd.addAll(createNewEvents(accountBlockingDurations, billingEvents, subscription, context));
@@ -150,9 +150,16 @@ public class BlockingCalculator {
return !(billingEventsToAdd.isEmpty() && billingEventsToRemove.isEmpty());
}
- final List<BlockingState> getAggregateBlockingEventsPerSubscription(final Iterable<BlockingState> subscriptionBlockingEvents, final Iterable<BlockingState> bundleBlockingEvents, final Iterable<BlockingState> accountBlockingEvents) {
+ final List<BlockingState> getAggregateBlockingEventsPerSubscription(@Nullable final DateTime subscriptionEndDate, final Iterable<BlockingState> subscriptionBlockingEvents, final Iterable<BlockingState> bundleBlockingEvents, final Iterable<BlockingState> accountBlockingEvents) {
final Iterable<BlockingState> tmp = Iterables.concat(subscriptionBlockingEvents, bundleBlockingEvents, accountBlockingEvents);
- final List<BlockingState> result = Lists.newArrayList(tmp);
+ final Iterable<BlockingState> allEventsPriorToCancelDate = Iterables.filter(tmp,
+ new Predicate<BlockingState>() {
+ @Override
+ public boolean apply(final BlockingState input) {
+ return subscriptionEndDate == null || input.getEffectiveDate().compareTo(subscriptionEndDate) <= 0;
+ }
+ });
+ final List<BlockingState> result = Lists.newArrayList(allEventsPriorToCancelDate);
Collections.sort(result);
return result;
}