diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
index 796bd34..b9ddef6 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
@@ -98,78 +98,91 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
for (BlockingState bs : allBlockingStates) {
final LocalDate bsEffectiveDate = new LocalDate(bs.getEffectiveDate(), accountTimeZone);
- // In the beginning there was nothing...
- final Map<UUID, Boolean> isBlockedBillingMap = new HashMap<UUID, Boolean>();
- final Map<UUID, Boolean> isBlockedEntitlementMap = new HashMap<UUID, Boolean>();
- for (UUID uuid : allEntitlementUUIDs) {
- isBlockedBillingMap.put(uuid, Boolean.TRUE);
- isBlockedEntitlementMap.put(uuid, Boolean.TRUE);
- }
final List<SubscriptionEvent> newEvents = new ArrayList<SubscriptionEvent>();
- int index = insertFromBlockingEvent(accountTimeZone, allEntitlementUUIDs, result, bs, bsEffectiveDate, isBlockedBillingMap, isBlockedEntitlementMap, newEvents);
+ int index = insertFromBlockingEvent(accountTimeZone, allEntitlementUUIDs, result, bs, bsEffectiveDate, newEvents);
result.addAll(index, newEvents);
}
return result;
}
- private int insertFromBlockingEvent(final DateTimeZone accountTimeZone, final Set<UUID> allEntitlementUUIDs, final LinkedList<SubscriptionEvent> result, final BlockingState bs, final LocalDate bsEffectiveDate, final Map<UUID, Boolean> blockedBillingMap, final Map<UUID, Boolean> blockedEntitlementMap, final List<SubscriptionEvent> newEvents) {
+ private int insertFromBlockingEvent(final DateTimeZone accountTimeZone, final Set<UUID> allEntitlementUUIDs, final LinkedList<SubscriptionEvent> result, final BlockingState bs, final LocalDate bsEffectiveDate, final List<SubscriptionEvent> newEvents) {
+
+
+ // In the beginning there was nothing...
+ final Map<UUID, Boolean> blockedEntitlementMap = new HashMap<UUID, Boolean>();
+ final Map<UUID, Boolean> blockedBillingMap = new HashMap<UUID, Boolean>();
+ for (UUID uuid : allEntitlementUUIDs) {
+ blockedEntitlementMap.put(uuid, Boolean.TRUE);
+ blockedBillingMap.put(uuid, Boolean.TRUE);
+ }
+
int index = -1;
final Iterator<SubscriptionEvent> it = result.iterator();
+ DefaultSubscriptionEvent cur = null;
while (it.hasNext()) {
- final DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) it.next();
+ cur = (DefaultSubscriptionEvent) it.next();
index++;
- switch (cur.getSubscriptionEventType()) {
- case START_ENTITLEMENT:
- blockedEntitlementMap.put(cur.getEntitlementId(), Boolean.FALSE);
- break;
- case START_BILLING:
- blockedBillingMap.put(cur.getEntitlementId(), Boolean.FALSE);
- break;
- case PAUSE_ENTITLEMENT:
- case STOP_ENTITLEMENT:
- blockedEntitlementMap.put(cur.getEntitlementId(), Boolean.TRUE);
- break;
- case PAUSE_BILLING:
- case STOP_BILLING:
- blockedBillingMap.put(cur.getEntitlementId(), Boolean.TRUE);
- break;
- }
-
final int compEffectiveDate = bsEffectiveDate.compareTo(cur.getEffectiveDate());
- if (compEffectiveDate < 0 ||
- (compEffectiveDate == 0 && bs.getCreatedDate().compareTo(cur.getCreatedDate()) <= 0)) {
- continue;
+ final boolean shouldContinue = (compEffectiveDate > 0 ||
+ (compEffectiveDate == 0 && bs.getCreatedDate().compareTo(cur.getCreatedDate()) >= 0));
+
+ if (shouldContinue) {
+ switch (cur.getSubscriptionEventType()) {
+ case START_ENTITLEMENT:
+ blockedEntitlementMap.put(cur.getEntitlementId(), Boolean.FALSE);
+ break;
+ case START_BILLING:
+ blockedBillingMap.put(cur.getEntitlementId(), Boolean.FALSE);
+ break;
+ case PAUSE_ENTITLEMENT:
+ case STOP_ENTITLEMENT:
+ blockedEntitlementMap.put(cur.getEntitlementId(), Boolean.TRUE);
+ break;
+ case PAUSE_BILLING:
+ case STOP_BILLING:
+ blockedBillingMap.put(cur.getEntitlementId(), Boolean.TRUE);
+ break;
+ }
+ } else {
+ break;
}
+ }
- final DefaultSubscriptionEvent next = it.hasNext() ? (DefaultSubscriptionEvent) it.next() : null;
- final List<UUID> targetEntitlementIds = bs.getType() == BlockingStateType.SUBSCRIPTION ? ImmutableList.<UUID>of(bs.getId()) :
- ImmutableList.<UUID>copyOf(allEntitlementUUIDs);
- for (UUID target : targetEntitlementIds) {
+ final DefaultSubscriptionEvent next = it.hasNext() ? (DefaultSubscriptionEvent) it.next() : null;
- final Boolean isResumeEntitlement = (blockedEntitlementMap.get(bs.getId()) && !bs.isBlockEntitlement());
- final Boolean isPauseEntitlement = (!blockedEntitlementMap.get(bs.getId()) && bs.isBlockEntitlement());
- final Boolean isResumeBilling = (blockedBillingMap.get(bs.getId()) && !bs.isBlockBilling());
- final Boolean isPauseBilling = (!blockedBillingMap.get(bs.getId()) && bs.isBlockBilling());
- final Boolean isServiceStateChange = !(isResumeEntitlement || isPauseEntitlement || isResumeBilling || isPauseBilling);
+ final List<UUID> targetEntitlementIds = bs.getType() == BlockingStateType.SUBSCRIPTION ? ImmutableList.<UUID>of(bs.getBlockedId()) :
+ ImmutableList.<UUID>copyOf(allEntitlementUUIDs);
+ for (UUID target : targetEntitlementIds) {
- if (isResumeEntitlement) {
- newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.RESUME_ENTITLEMENT, accountTimeZone));
- } else if (isPauseEntitlement) {
- newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.PAUSE_ENTITLEMENT, accountTimeZone));
- }
- if (isResumeBilling) {
- newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.RESUME_BILLING, accountTimeZone));
- } else if (isPauseBilling) {
- newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.PAUSE_BILLING, accountTimeZone));
- }
- if (isServiceStateChange) {
- newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.SERVICE_STATE_CHANGE, accountTimeZone));
- }
+ // If the blocking state is ENT_STATE_CANCELLED there is nothing else to look at, just insert the event
+ if (bs.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
+ newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.STOP_ENTITLEMENT, accountTimeZone));
+ continue;
+ }
+
+ // If not, figure out from the existing state, what that new event should be
+ final Boolean isResumeEntitlement = (blockedEntitlementMap.get(target) && !bs.isBlockEntitlement());
+ final Boolean isPauseEntitlement = (!blockedEntitlementMap.get(target) && bs.isBlockEntitlement());
+ final Boolean isResumeBilling = (blockedBillingMap.get(target) && !bs.isBlockBilling());
+ final Boolean isPauseBilling = (!blockedBillingMap.get(target) && bs.isBlockBilling());
+ final Boolean isServiceStateChange = !(isResumeEntitlement || isPauseEntitlement || isResumeBilling || isPauseBilling);
+
+ if (isResumeEntitlement) {
+ newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.RESUME_ENTITLEMENT, accountTimeZone));
+ } else if (isPauseEntitlement) {
+ newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.PAUSE_ENTITLEMENT, accountTimeZone));
+ }
+ if (isResumeBilling) {
+ newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.RESUME_BILLING, accountTimeZone));
+ } else if (isPauseBilling) {
+ newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.PAUSE_BILLING, accountTimeZone));
+ }
+ if (isServiceStateChange) {
+ newEvents.add(toSubscriptionEvent(cur, next, target, bs, SubscriptionEventType.SERVICE_STATE_CHANGE, accountTimeZone));
}
- break;
}
return index;
}
@@ -242,10 +255,10 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
// Same EffectiveDate and CreatedDate but order by ID
break;
} else if (compUUID == 0) {
- if (event.getSubscriptionEventType().ordinal() < cur.getSubscriptionEventType().ordinal()) {
- // Same EffectiveDate, CreatedDate and ID, but event type is lower -- as described in enum
- break;
- }
+ if (event.getSubscriptionEventType().ordinal() < cur.getSubscriptionEventType().ordinal()) {
+ // Same EffectiveDate, CreatedDate and ID, but event type is lower -- as described in enum
+ break;
+ }
}
}
}
@@ -264,16 +277,16 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
in.isBlockBilling(),
in.getService(),
in.getStateName(),
- prev.getNextProduct(),
- prev.getNextPlan(),
- prev.getNextPhase(),
- prev.getNextPriceList(),
- prev.getNextBillingPeriod(),
- next.getPrevProduct(),
- next.getPrevPlan(),
- next.getPrevPhase(),
- next.getPrevPriceList(),
- next.getPrevBillingPeriod(),
+ prev != null ? prev.getNextProduct() : null,
+ prev != null ? prev.getNextPlan() : null,
+ prev != null ? prev.getNextPhase() : null,
+ prev != null ? prev.getNextPriceList() : null,
+ prev != null ? prev.getNextBillingPeriod() : null,
+ next != null ? next.getPrevProduct() : null,
+ next != null ? next.getPrevPlan() : null,
+ next != null ? next.getPrevPhase() : null,
+ next != null ? next.getPrevPriceList() : null,
+ next != null ? next.getPrevBillingPeriod() : null,
in.getCreatedDate());
}
@@ -377,7 +390,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
private final DateTime createdDate;
-
private DefaultSubscriptionEvent(final UUID id,
final UUID entitlementId,
final LocalDate effectiveDate,
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index 2c35cd8..1c7370c 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -19,6 +19,7 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceList;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.DefaultEntitlementService;
import com.ning.billing.entitlement.EntitlementTestSuiteNoDB;
import com.ning.billing.entitlement.api.SubscriptionBundleTimeline.SubscriptionEvent;
import com.ning.billing.entitlement.api.SubscriptionBundleTimeline.SubscriptionEventType;
@@ -27,6 +28,7 @@ import com.ning.billing.subscription.api.user.SubscriptionBaseTransition;
import com.ning.billing.subscription.api.user.SubscriptionBaseTransitionData;
import com.ning.billing.subscription.events.SubscriptionBaseEvent.EventType;
import com.ning.billing.subscription.events.user.ApiEventType;
+import com.ning.billing.util.svcapi.junction.DefaultBlockingState;
import static org.testng.Assert.assertEquals;
@@ -41,7 +43,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
}
@Test(groups = "fast")
- public void testSimple() throws CatalogApiException {
+ public void testOneEntitlementNoBlockingStates() throws CatalogApiException {
clock.setDay(new LocalDate(2013, 1, 1));
@@ -96,6 +98,81 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
}
+
+
+ @Test(groups = "fast")
+ public void testOneEntitlementWithBlockingStatesSubscription() throws CatalogApiException {
+
+ clock.setDay(new LocalDate(2013, 1, 1));
+
+ final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+ final UUID accountId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
+ final String externalKey = "foo";
+
+
+ final UUID entitlementId = UUID.randomUUID();
+
+ final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+ final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+
+ final DateTime requestedDate = new DateTime();
+ DateTime effectiveDate = new DateTime(2013, 1, 1, 15, 43, 25, 0, DateTimeZone.UTC);
+ final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow());
+ allTransitions.add(tr1);
+
+ effectiveDate = effectiveDate.plusDays(30);
+ clock.addDays(30);
+ final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow());
+ allTransitions.add(tr2);
+
+ effectiveDate = effectiveDate.plusDays(5);
+ clock.addDays(5);
+ final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+ blockingStates.add(bs1);
+
+ effectiveDate = effectiveDate.plusDays(15);
+ clock.addDays(15);
+ final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow());
+ allTransitions.add(tr3);
+ final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+ DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+ true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+ blockingStates.add(bs2);
+
+ final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+ final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
+ entitlements.add(entitlement);
+
+
+
+
+ final DefaultSubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
+
+ List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+ assertEquals(events.size(), 6);
+
+ assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(tr2.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
+ assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
+ assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+
+ assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+ assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+ assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+ assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+ assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
+ assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+ }
+
+
+
+
private DefaultEntitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
final DefaultEntitlement result = Mockito.mock(DefaultEntitlement.class);