diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
index 8f49b8a..25842b7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscription.java
@@ -37,7 +37,16 @@ public class DefaultSubscription extends DefaultEntitlement implements Subscript
@Override
public LocalDate getBillingEndDate() {
- final DateTime futureOrCurrentEndDate = getSubscriptionBase().getEndDate() != null ? getSubscriptionBase().getEndDate() : getSubscriptionBase().getFutureEndDate();
+ final DateTime futureOrCurrentEndDateForSubscription = getSubscriptionBase().getEndDate() != null ? getSubscriptionBase().getEndDate() : getSubscriptionBase().getFutureEndDate();
+ final DateTime futureOrCurrentEndDateForBaseSubscription = getEventsStream().getBaseSubscription().getEndDate() != null ? getEventsStream().getBaseSubscription().getEndDate() : getEventsStream().getBaseSubscription().getFutureEndDate();
+
+ final DateTime futureOrCurrentEndDate;
+ if (futureOrCurrentEndDateForBaseSubscription != null && futureOrCurrentEndDateForBaseSubscription.isBefore(futureOrCurrentEndDateForSubscription)) {
+ futureOrCurrentEndDate = futureOrCurrentEndDateForBaseSubscription;
+ } else {
+ futureOrCurrentEndDate = futureOrCurrentEndDateForSubscription;
+ }
+
return futureOrCurrentEndDate != null ? new LocalDate(futureOrCurrentEndDate, getAccount().getTimeZone()) : null;
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
index bb9431c..a154d28 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
@@ -215,6 +215,9 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
throw new RuntimeException(e);
}
+ // Retrieve the cancellation blocking state on disk, if it exists (will be used later)
+ final BlockingState cancellationBlockingStateOnDisk = findEntitlementCancellationBlockingState(blockableId, blockingStatesOnDiskCopy);
+
// Compute the blocking states not on disk for all base subscriptions
final DateTime now = clock.getUTCNow();
for (final SubscriptionBase baseSubscription : baseSubscriptionsToConsider) {
@@ -226,18 +229,31 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
throw new RuntimeException(e);
}
+ // First, check to see if the base entitlement is cancelled. If so, cancel the
final Collection<BlockingState> blockingStatesNotOnDisk = eventsStream.computeAddonsBlockingStatesForFutureSubscriptionBaseEvents();
// Inject the extra blocking states into the stream if needed
for (final BlockingState blockingState : blockingStatesNotOnDisk) {
- if ((blockingStateType == null ||
- // In case we're coming from getBlockingHistoryForService / getBlockingAll, make sure we don't add
- // blocking states for other add-ons on that base subscription
- (BlockingStateType.SUBSCRIPTION.equals(blockingStateType) && blockingState.getBlockedId().equals(blockableId))) &&
- // If this entitlement is actually already cancelled, don't add the cancellation event we computed from subscription events (wrong)
- !isEntitlementCancelled(blockingState.getBlockedId(), blockingStatesOnDiskCopy)) {
+ // If this entitlement is actually already cancelled, add the cancellation event we computed
+ // only if it's prior to the blocking state on disk (e.g. add-on future cancelled but base plan cancelled earlier).
+ final boolean overrideCancellationBlockingStateOnDisk = cancellationBlockingStateOnDisk != null &&
+ isEntitlementCancellationBlockingState(blockingState) &&
+ blockingState.getEffectiveDate().isBefore(cancellationBlockingStateOnDisk.getEffectiveDate());
+
+ if ((
+ blockingStateType == null ||
+ // In case we're coming from getBlockingHistoryForService / getBlockingAll, make sure we don't add
+ // blocking states for other add-ons on that base subscription
+ (BlockingStateType.SUBSCRIPTION.equals(blockingStateType) && blockingState.getBlockedId().equals(blockableId))
+ ) && (
+ cancellationBlockingStateOnDisk == null || overrideCancellationBlockingStateOnDisk
+ )) {
final BlockingStateModelDao blockingStateModelDao = new BlockingStateModelDao(blockingState, now, now);
blockingStatesOnDiskCopy.add(BlockingStateModelDao.toBlockingState(blockingStateModelDao));
+
+ if (overrideCancellationBlockingStateOnDisk) {
+ blockingStatesOnDiskCopy.remove(cancellationBlockingStateOnDisk);
+ }
}
}
}
@@ -246,18 +262,25 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
return BLOCKING_STATE_ORDERING.immutableSortedCopy(blockingStatesOnDiskCopy);
}
- private boolean isEntitlementCancelled(final UUID blockedId, final Iterable<BlockingState> blockingStates) {
- // If this entitlement is already cancelled, there is nothing to do
- return Iterables.<BlockingState>tryFind(blockingStates,
+ private BlockingState findEntitlementCancellationBlockingState(@Nullable final UUID blockedId, final Iterable<BlockingState> blockingStatesOnDisk) {
+ if (blockedId == null) {
+ return null;
+ }
+
+ return Iterables.<BlockingState>tryFind(blockingStatesOnDisk,
new Predicate<BlockingState>() {
@Override
public boolean apply(final BlockingState input) {
return input.getBlockedId().equals(blockedId) &&
- BlockingStateType.SUBSCRIPTION.equals(input.getType()) &&
- EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
- DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(input.getStateName());
+ isEntitlementCancellationBlockingState(input);
}
})
- .orNull() != null;
+ .orNull();
+ }
+
+ private static boolean isEntitlementCancellationBlockingState(final BlockingState blockingState) {
+ return BlockingStateType.SUBSCRIPTION.equals(blockingState.getType()) &&
+ EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(blockingState.getService()) &&
+ DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(blockingState.getStateName());
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
index 2bc92f5..a0c387c 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -318,9 +318,6 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
final Entitlement addOn2Entitlement = entitlementApi.addEntitlement(baseEntitlement.getBundleId(), addOn2Spec, initialDate, callContext);
assertListenerStatus();
- final LocalDate baseCancellationDate = new LocalDate(2013, 10, 10);
- baseEntitlement.cancelEntitlementWithDate(baseCancellationDate, true, callContext);
-
// Date prior to the base cancellation date to verify it is not impacted by the base cancellation (in contrary to the second add-on)
final LocalDate addOn1CancellationDate = new LocalDate(2013, 9, 9);
addOnEntitlement.cancelEntitlementWithDate(addOn1CancellationDate, true, callContext);
@@ -328,13 +325,22 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
final LocalDate addOn2CancellationDate = new LocalDate(2013, 11, 11);
addOn2Entitlement.cancelEntitlementWithDate(addOn2CancellationDate, true, callContext);
+ // Before the base entitlement is cancelled, respect the specified cancellation date
+ Assert.assertEquals(entitlementApi.getEntitlementForId(addOn2Entitlement.getId(), callContext).getEffectiveEndDate(), addOn2CancellationDate);
+
+ final LocalDate baseCancellationDate = new LocalDate(2013, 10, 10);
+ baseEntitlement.cancelEntitlementWithDate(baseCancellationDate, true, callContext);
+
+ // After the base entitlement is cancelled, verify the date is overridden
+ Assert.assertEquals(entitlementApi.getEntitlementForId(addOn2Entitlement.getId(), callContext).getEffectiveEndDate(), baseCancellationDate);
+
// No further event yet
assertListenerStatus();
// Verify the cancellation dates
Assert.assertEquals(entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext).getEffectiveEndDate(), baseCancellationDate);
Assert.assertEquals(entitlementApi.getEntitlementForId(addOnEntitlement.getId(), callContext).getEffectiveEndDate(), addOn1CancellationDate);
- Assert.assertEquals(entitlementApi.getEntitlementForId(addOn2Entitlement.getId(), callContext).getEffectiveEndDate(), addOn2CancellationDate);
+ Assert.assertEquals(entitlementApi.getEntitlementForId(addOn2Entitlement.getId(), callContext).getEffectiveEndDate(), baseCancellationDate);
testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK);
clock.setDay(new LocalDate(2013, 10, 30));