killbill-aplcache

entitlement: Fix issue in cancellation logic when specifying

5/6/2016 8:28:52 PM

Details

diff --git a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
index 188e714..99363ed 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -46,6 +46,11 @@ public interface EventsStream {
 
     LocalDate getEntitlementEffectiveEndDate();
 
+    DateTime getEntitlementEffectiveStartDateTime();
+
+    DateTime getEntitlementEffectiveEndDateTime();
+
+
     SubscriptionBase getSubscriptionBase();
 
     SubscriptionBase getBasePlanSubscriptionBase();
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index 4cf7938..72fb0c2 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -321,7 +321,8 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
                 }
 
                 final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
-                final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTime(entitlementEffectiveDate, contextWithValidAccountRecordId);
+
+                final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), contextWithValidAccountRecordId);
                 try {
                     if (overrideBillingEffectiveDate) {
                         getSubscriptionBase().cancelWithDate(effectiveCancelDate, callContext);
@@ -463,15 +464,15 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
                     throw new EntitlementApiException(e);
                 }
 
-                final DateTime entitlementEffectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEntitlementEffectiveDate(), contextWithValidAccountRecordId);
-                final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, entitlementEffectiveDate);
+                final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTimeWithMinimum(entitlementEffectiveDate, getEventsStream().getEntitlementEffectiveStartDateTime(), contextWithValidAccountRecordId);
+                final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveCancelDate);
                 final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
-                final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(entitlementEffectiveDate, notificationEvents, callContext, contextWithValidAccountRecordId);
+                final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
 
                 // Record the new state first, then insert the notifications to avoid race conditions
                 setBlockingStates(newBlockingState, addOnsBlockingStates, contextWithValidAccountRecordId);
                 for (final NotificationEvent notificationEvent : notificationEvents) {
-                    recordFutureNotification(entitlementEffectiveDate, notificationEvent, contextWithValidAccountRecordId);
+                    recordFutureNotification(effectiveCancelDate, notificationEvent, contextWithValidAccountRecordId);
                 }
 
                 return entitlementApi.getEntitlementForId(getId(), callContext);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
index dbc48e8..21845a5 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
@@ -38,6 +38,12 @@ public class EntitlementDateHelper {
         return requestedDate == null ? clock.getUTCNow() : callContext.toUTCDateTime(requestedDate);
     }
 
+
+    public DateTime fromLocalDateAndReferenceTimeWithMinimum(@Nullable final LocalDate requestedDate, final DateTime min, final InternalTenantContext callContext) throws EntitlementApiException {
+        final DateTime candidate = fromLocalDateAndReferenceTime(requestedDate, callContext);
+        return candidate.compareTo(min) < 0 ? min : candidate;
+    }
+
     /**
      * Check if the date portion of a date/time is before or equals at now (as returned by the clock).
      *
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index d7e4799..8c21972 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -74,7 +74,11 @@ public class DefaultEventsStream implements EventsStream {
     private BlockingAggregator blockingAggregator;
     private List<BlockingState> subscriptionEntitlementStates;
     private LocalDate entitlementEffectiveStartDate;
+    private DateTime entitlementEffectiveStartDateTime;
+
     private LocalDate entitlementEffectiveEndDate;
+    private DateTime entitlementEffectiveEndDateTime;
+
     private BlockingState entitlementStartEvent;
     private BlockingState entitlementCancelEvent;
     private EntitlementState entitlementState;
@@ -142,6 +146,16 @@ public class DefaultEventsStream implements EventsStream {
     }
 
     @Override
+    public DateTime getEntitlementEffectiveStartDateTime() {
+        return entitlementEffectiveStartDateTime;
+    }
+
+    @Override
+    public DateTime getEntitlementEffectiveEndDateTime() {
+        return entitlementEffectiveEndDateTime;
+    }
+
+    @Override
     public EntitlementState getEntitlementState() {
         return entitlementState;
     }
@@ -410,9 +424,11 @@ public class DefaultEventsStream implements EventsStream {
                                                                   }).orNull();
 
         // Note that we still default to subscriptionBase.startDate (for compatibility issue where ENT_STATE_START does not exist)
-        entitlementEffectiveStartDate = entitlementStartEvent != null ?
-                                        internalTenantContext.toLocalDate(entitlementStartEvent.getEffectiveDate()) :
-                                        internalTenantContext.toLocalDate(getSubscriptionBase().getStartDate());
+        entitlementEffectiveStartDateTime = entitlementStartEvent != null ?
+                                            entitlementStartEvent.getEffectiveDate() :
+                                            getSubscriptionBase().getStartDate();
+        entitlementEffectiveStartDate = internalTenantContext.toLocalDate(entitlementEffectiveStartDateTime);
+
     }
 
     private void computeEntitlementCancelEvent() {
@@ -423,7 +439,8 @@ public class DefaultEventsStream implements EventsStream {
                                                                           return DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(input.getStateName());
                                                                       }
                                                                   }).orNull();
-        entitlementEffectiveEndDate = entitlementCancelEvent != null ? internalTenantContext.toLocalDate(entitlementCancelEvent.getEffectiveDate()) : null;
+        entitlementEffectiveEndDateTime =  entitlementCancelEvent != null ? entitlementCancelEvent.getEffectiveDate() : null;
+        entitlementEffectiveEndDate = entitlementEffectiveEndDateTime != null ? internalTenantContext.toLocalDate(entitlementEffectiveEndDateTime) : null;
     }
 
     private void computeStateForEntitlement() {