diff --git a/api/src/main/java/com/ning/billing/entitlement/EventsStream.java b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
index b570fd3..8cb930f 100644
--- a/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
@@ -60,6 +60,8 @@ public interface EventsStream {
Collection<BlockingState> getPendingEntitlementCancellationEvents();
+ BlockingState getEntitlementCancellationEvent();
+
// All blocking states for the account, associated bundle or subscription
Collection<BlockingState> getBlockingStates();
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
index ac479f7..5576a98 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
@@ -289,15 +289,28 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
// Get the latest state from disk
refresh(callContext);
- if (eventsStream.isEntitlementCancelled() || eventsStream.isSubscriptionCancelled()) {
+ if (eventsStream.isSubscriptionCancelled()) {
throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
}
- // Reactivate entitlements
- // See also https://github.com/killbill/killbill/issues/111
final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
- for (final BlockingState futureCancellation : eventsStream.getPendingEntitlementCancellationEvents()) {
- blockingStateDao.unactiveBlockingState(futureCancellation.getId(), contextWithValidAccountRecordId);
+ final Collection<BlockingState> pendingEntitlementCancellationEvents = eventsStream.getPendingEntitlementCancellationEvents();
+ if (eventsStream.isEntitlementCancelled()) {
+ final BlockingState cancellationEvent = eventsStream.getEntitlementCancellationEvent();
+ blockingStateDao.unactiveBlockingState(cancellationEvent.getId(), contextWithValidAccountRecordId);
+ } else if (pendingEntitlementCancellationEvents.size() > 0) {
+ // Reactivate entitlements
+ // See also https://github.com/killbill/killbill/issues/111
+ //
+ // Today we only support cancellation at SUBSCRIPTION level (Not ACCOUNT or BUNDLE), so we should really have only
+ // one future event in the list
+ //
+ for (final BlockingState futureCancellation : pendingEntitlementCancellationEvents) {
+ blockingStateDao.unactiveBlockingState(futureCancellation.getId(), contextWithValidAccountRecordId);
+ }
+ } else {
+ // Entitlement is NOT cancelled (or future cancelled), there is nothing to do
+ throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
}
// If billing was previously cancelled, reactivate
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java
index 7a735cb..1b28830 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -190,6 +190,11 @@ public class DefaultEventsStream implements EventsStream {
return getPendingEntitlementEvents(DefaultEntitlementApi.ENT_STATE_CANCELLED);
}
+ @Override
+ public BlockingState getEntitlementCancellationEvent() {
+ return entitlementCancelEvent;
+ }
+
public Collection<BlockingState> getPendingEntitlementEvents(final String... types) {
final List<String> typeList = ImmutableList.<String>copyOf(types);
return Collections2.<BlockingState>filter(subscriptionEntitlementStates,
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
index e2ff2d8..caa7178 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -62,15 +62,12 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
testListener.pushExpectedEvent(NextEvent.CREATE);
final Entitlement addOnEntitlement = entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, initialDate, callContext);
assertListenerStatus();
-
- /*
- // TODO It looks like we don't check if there is a future cancellation. Maybe we should?
try {
entitlement.uncancelEntitlement(callContext);
Assert.fail("Entitlement hasn't been cancelled yet");
} catch (final EntitlementApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.SUB_CANCEL_BAD_STATE.getCode());
- }*/
+ }
clock.addDays(3);
@@ -108,6 +105,40 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
}
}
+
+ @Test(groups = "slow")
+ public void testUncancelEffectiveCancelledEntitlement() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ // Keep the same object for the whole test, to make sure we refresh its state before r/w calls
+ testListener.pushExpectedEvent(NextEvent.CREATE);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+ assertListenerStatus();
+
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ clock.addDays(30);
+ assertListenerStatus();
+ subscriptionInternalApi.setChargedThroughDate(entitlement.getId(), clock.getUTCNow().plusMonths(1), internalCallContext);
+
+ final LocalDate entitlementCancelledDate = clock.getToday(account.getTimeZone());
+ testListener.pushExpectedEvent(NextEvent.BLOCK);
+ final Entitlement cancelledEntitlement = entitlement.cancelEntitlementWithDateOverrideBillingPolicy(clock.getToday(account.getTimeZone()), BillingActionPolicy.END_OF_TERM, callContext);
+ assertListenerStatus();
+ Assert.assertEquals(cancelledEntitlement.getEffectiveEndDate(), entitlementCancelledDate);
+
+ testListener.pushExpectedEvent(NextEvent.UNCANCEL);
+ cancelledEntitlement.uncancelEntitlement(callContext);
+ assertListenerStatus();
+
+ final Entitlement reactivatedEntitlement = entitlementApi.getEntitlementForId(cancelledEntitlement.getId(), callContext);
+ Assert.assertNull(reactivatedEntitlement.getEffectiveEndDate());
+ }
+
@Test(groups = "slow")
public void testCreateEntitlementWithCheck() throws AccountApiException, EntitlementApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);