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() {