killbill-memoizeit
Changes
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 11(+10 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java 70(+60 -10)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java 10(+6 -4)
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 99363ed..42d9f4a 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EventsStream.java
@@ -50,7 +50,6 @@ public interface EventsStream {
DateTime getEntitlementEffectiveEndDateTime();
-
SubscriptionBase getSubscriptionBase();
SubscriptionBase getBasePlanSubscriptionBase();
@@ -63,6 +62,8 @@ public interface EventsStream {
boolean isSubscriptionCancelled();
+ int getDefaultBillCycleDayLocal();
+
Collection<BlockingState> getPendingEntitlementCancellationEvents();
BlockingState getEntitlementCancellationEvent();
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java b/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java
index 0265dc1..f84d013 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java
@@ -29,4 +29,5 @@ public interface BillingInternalApi {
* @return an ordered list of billing event for the given accounts
*/
public BillingEventSet getBillingEventsForAccountAndUpdateAccountBCD(UUID accountId, DryRunArguments dryRunArguments, InternalCallContext context) throws CatalogApiException, AccountApiException;
+
}
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index c194b4c..2556f87 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -25,11 +25,13 @@ import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.api.EntitlementAOStatusDryRun;
@@ -100,4 +102,8 @@ public interface SubscriptionBaseInternalApi {
public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
+
+ public int getDefaultBillCycleDayLocal(final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final DateTimeZone accountTimeZone, final int accountBillCycleDayLocal, final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException;
+
+
}
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 5a7eae4..1cb7ffc 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
@@ -93,7 +93,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
// Refresh-able
protected EventsStream eventsStream;
-
public DefaultEntitlement(final UUID accountId, final UUID entitlementId, final EventsStreamBuilder eventsStreamBuilder,
final EntitlementApi entitlementApi, final EntitlementPluginExecution pluginExecution, final BlockingStateDao blockingStateDao,
final SubscriptionBaseInternalApi subscriptionInternalApi, final BlockingChecker checker,
@@ -281,7 +280,8 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
@Override
public Integer getBillCycleDayLocal() {
- return getSubscriptionBase().getBillCycleDayLocal();
+ final Integer perSubscriptionBillCycleDayLocal = getSubscriptionBase().getBillCycleDayLocal();
+ return perSubscriptionBillCycleDayLocal != null ? perSubscriptionBillCycleDayLocal : eventsStream.getDefaultBillCycleDayLocal();
}
@Override
@@ -319,7 +319,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
properties,
callContext);
-
final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
@Override
@@ -358,7 +357,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
return pluginExecution.executeWithPlugin(cancelEntitlementWithPlugin, pluginContext);
}
-
@Override
public void uncancelEntitlement(final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
@@ -431,7 +429,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
return cancelEntitlementWithDateOverrideBillingPolicy(cancellationDate, billingPolicy, properties, callContext);
}
-
// See also EntitlementInternalApi#cancel for the bulk API
@Override
public Entitlement cancelEntitlementWithDateOverrideBillingPolicy(@Nullable final LocalDate entitlementEffectiveDate, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
@@ -511,7 +508,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
@Override
public Entitlement changePlan(final String productName, final BillingPeriod billingPeriod, final String priceList, final List<PlanPhasePriceOverride> overrides, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
-
checkForPermissions(Permission.ENTITLEMENT_CAN_CHANGE_PLAN, callContext);
// Get the latest state from disk
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 8c21972..19d7cb9 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
@@ -70,6 +70,7 @@ public class DefaultEventsStream implements EventsStream {
private final List<SubscriptionBase> allSubscriptionsForBundle;
private final InternalTenantContext internalTenantContext;
private final DateTime utcNow;
+ private final int defaultBillCycleDayLocal;
private BlockingAggregator blockingAggregator;
private List<BlockingState> subscriptionEntitlementStates;
@@ -86,7 +87,9 @@ public class DefaultEventsStream implements EventsStream {
public DefaultEventsStream(final ImmutableAccountData account, final SubscriptionBaseBundle bundle,
final List<BlockingState> blockingStates, final BlockingChecker blockingChecker,
@Nullable final SubscriptionBase baseSubscription, final SubscriptionBase subscription,
- final List<SubscriptionBase> allSubscriptionsForBundle, final InternalTenantContext contextWithValidAccountRecordId, final DateTime utcNow) {
+ final List<SubscriptionBase> allSubscriptionsForBundle,
+ final int defaultBillCycleDayLocal,
+ final InternalTenantContext contextWithValidAccountRecordId, final DateTime utcNow) {
this.account = account;
this.bundle = bundle;
this.blockingStates = blockingStates;
@@ -94,6 +97,7 @@ public class DefaultEventsStream implements EventsStream {
this.baseSubscription = baseSubscription;
this.subscription = subscription;
this.allSubscriptionsForBundle = allSubscriptionsForBundle;
+ this.defaultBillCycleDayLocal = defaultBillCycleDayLocal;
this.internalTenantContext = contextWithValidAccountRecordId;
this.utcNow = utcNow;
@@ -194,6 +198,11 @@ public class DefaultEventsStream implements EventsStream {
}
@Override
+ public int getDefaultBillCycleDayLocal() {
+ return defaultBillCycleDayLocal;
+ }
+
+ @Override
public Collection<BlockingState> getBlockingStates() {
return blockingStates;
}
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
index dc6a097..6cee88b 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/EventsStreamBuilder.java
@@ -35,11 +35,17 @@ import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.account.api.ImmutableAccountData;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PhaseType;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.AccountEventsStreams;
import org.killbill.billing.entitlement.EventsStream;
import org.killbill.billing.entitlement.api.BlockingState;
import org.killbill.billing.entitlement.api.BlockingStateType;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementApiException;
import org.killbill.billing.entitlement.api.svcs.DefaultAccountEventsStreams;
import org.killbill.billing.entitlement.block.BlockingChecker;
@@ -88,7 +94,6 @@ public class EventsStreamBuilder {
this.checker = checker;
this.clock = clock;
this.internalCallContextFactory = internalCallContextFactory;
-
this.defaultBlockingStateDao = new DefaultBlockingStateDao(dbi, clock, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory);
this.blockingStateDao = new OptimizedProxyBlockingStateDao(this, subscriptionInternalApi, dbi, clock, notificationQueueService, eventBus, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory);
}
@@ -324,15 +329,60 @@ public class EventsStreamBuilder {
final List<SubscriptionBase> allSubscriptionsForBundle,
final List<BlockingState> blockingStates,
final InternalTenantContext internalTenantContext) throws EntitlementApiException {
- return new DefaultEventsStream(account,
- bundle,
- blockingStates,
- checker,
- baseSubscription,
- subscription,
- allSubscriptionsForBundle,
- internalTenantContext,
- clock.getUTCNow());
+
+
+ try {
+ int accountBCD = accountInternalApi.getBCD(account.getId(), internalTenantContext);
+ int defaultAlignmentDay = subscriptionInternalApi.getDefaultBillCycleDayLocal(subscription, baseSubscription, createPlanPhaseSpecifier(subscription), account.getTimeZone(), accountBCD, clock.getUTCNow(), internalTenantContext);
+ return new DefaultEventsStream(account,
+ bundle,
+ blockingStates,
+ checker,
+ baseSubscription,
+ subscription,
+ allSubscriptionsForBundle,
+ defaultAlignmentDay,
+ internalTenantContext,
+ clock.getUTCNow());
+ } catch (final SubscriptionBaseApiException e) {
+ throw new EntitlementApiException(e);
+ } catch (final AccountApiException e) {
+ throw new EntitlementApiException(e);
+ }
+ }
+
+ private PlanPhaseSpecifier createPlanPhaseSpecifier(final SubscriptionBase subscription) {
+
+ final String lastActiveProductName;
+ final BillingPeriod billingPeriod;
+ final ProductCategory productCategory;
+ final String priceListName;
+ final PhaseType phaseType;
+
+ if (subscription.getState() == EntitlementState.PENDING) {
+ final SubscriptionBaseTransition transition = subscription.getPendingTransition();
+ final Product pendingProduct = transition.getNextPlan().getProduct();
+ lastActiveProductName = pendingProduct.getName();
+ productCategory = pendingProduct.getCategory();
+ final PlanPhase pendingPlanPhase = transition.getNextPhase();
+ billingPeriod = pendingPlanPhase.getRecurring() != null ? pendingPlanPhase.getRecurring().getBillingPeriod() : BillingPeriod.NO_BILLING_PERIOD;
+ priceListName = transition.getNextPriceList().getName();
+ phaseType = transition.getNextPhase().getPhaseType();
+ } else {
+ final Product lastActiveProduct = subscription.getLastActiveProduct();
+ lastActiveProductName = lastActiveProduct.getName();
+ productCategory = lastActiveProduct.getCategory();
+ final PlanPhase lastActivePlanPhase = subscription.getLastActivePhase();
+ billingPeriod = lastActivePlanPhase.getRecurring() != null ? lastActivePlanPhase.getRecurring().getBillingPeriod() : BillingPeriod.NO_BILLING_PERIOD;
+ priceListName = subscription.getLastActivePlan().getPriceList().getName();
+ phaseType = subscription.getLastActivePhase().getPhaseType();
+ }
+ return new PlanPhaseSpecifier(lastActiveProductName,
+ productCategory,
+ billingPeriod,
+ priceListName,
+ phaseType);
+
}
private SubscriptionBase findBaseSubscription(final Iterable<SubscriptionBase> subscriptions) {
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index d6ca0c4..e5fc00c 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -26,7 +26,6 @@ import java.util.UUID;
import javax.annotation.Nullable;
-import org.joda.time.DateTime;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.account.api.AccountInternalApi;
@@ -56,6 +55,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.bcd.BillCycleDayCalculator;
import org.killbill.billing.util.tag.ControlTagType;
import org.killbill.billing.util.tag.Tag;
import org.killbill.clock.Clock;
@@ -167,7 +167,8 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
result.getSubscriptionIdsWithAutoInvoiceOff().add(subscription.getId());
}
} else { // billing is not off
- addBillingEventsForSubscription(account, subscriptions, subscriptions.get(0), dryRunMode, context, result, skipSubscriptionsSet);
+ final SubscriptionBase baseSubscription = !subscriptions.isEmpty() ? subscriptions.get(0) : null;
+ addBillingEventsForSubscription(account, subscriptions, baseSubscription, dryRunMode, context, result, skipSubscriptionsSet);
}
}
}
@@ -214,7 +215,7 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
overridenBCD = transition.getNextBillCycleDayLocal() != null ? transition.getNextBillCycleDayLocal() : overridenBCD;
final int bcdLocal = overridenBCD != null ?
overridenBCD :
- calculateBcd(account, currentAccountBCD, baseSubscription, subscription, transition, context);
+ calculateBcdForTransition(baseSubscription, subscription, account, currentAccountBCD, transition, context);
if (currentAccountBCD == 0 && !updatedAccountBCD) {
accountApi.updateBCD(account.getExternalKey(), bcdLocal, context);
@@ -236,7 +237,8 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
}
}
- protected int calculateBcd(final ImmutableAccountData account, final int accountBillCycleDayLocal, final SubscriptionBase baseSubscription, final SubscriptionBase subscription, final EffectiveSubscriptionInternalEvent transition, final InternalCallContext context)
+
+ protected int calculateBcdForTransition(final SubscriptionBase baseSubscription, final SubscriptionBase subscription, final ImmutableAccountData account, final int accountBillCycleDayLocal, final EffectiveSubscriptionInternalEvent transition, final InternalCallContext context)
throws CatalogApiException, AccountApiException, SubscriptionBaseApiException {
final Catalog catalog = catalogService.getFullCatalog(context);
final BillingAlignment alignment = catalog.billingAlignment(getPlanPhaseSpecifierFromTransition(transition, context), transition.getEffectiveTransitionTime());
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index 1d51222..5a74086 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -312,6 +312,8 @@ public class TestEntitlement extends TestJaxrsBase {
final Subscription entitlementJson = createEntitlement(accountJson.getAccountId(), "99999", productName,
ProductCategory.BASE, term, true);
+ Assert.assertEquals(entitlementJson.getBillCycleDayLocal(), new Integer(25));
+
final Subscription updatedSubscription = new Subscription();
updatedSubscription.setSubscriptionId(entitlementJson.getSubscriptionId());
updatedSubscription.setBillCycleDayLocal(9);
@@ -319,8 +321,16 @@ public class TestEntitlement extends TestJaxrsBase {
final Subscription result = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
- // TODO depends on semantics for pending BCD change
- //Assert.assertEquals(result.getBillCycleDayLocal(), new Integer(9));
+ // Still shows as the 4 (BCD did not take effect)
+ Assert.assertEquals(result.getBillCycleDayLocal(), new Integer(25));
+
+ // 2012, 5, 9
+ clock.addDays(14);
+ crappyWaitForLackOfProperSynchonization();
+
+ final Subscription result2 = killBillClient.getSubscription(entitlementJson.getSubscriptionId());
+ // Still shows as the 4 (BCD did not take effect)
+ Assert.assertEquals(result2.getBillCycleDayLocal(), new Integer(9));
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 175207b..fa6964e 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -28,11 +28,13 @@ import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -75,6 +77,7 @@ import org.killbill.billing.subscription.events.bcd.BCDEvent;
import org.killbill.billing.subscription.events.bcd.BCDEventData;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.bcd.BillCycleDayCalculator;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
@@ -640,6 +643,17 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
dao.createBCDChangeEvent(subscription, bcdEvent, internalCallContext);
}
+ @Override
+ public int getDefaultBillCycleDayLocal(final SubscriptionBase subscription, final SubscriptionBase baseSubscription, final PlanPhaseSpecifier planPhaseSpecifier, final DateTimeZone accountTimeZone, final int accountBillCycleDayLocal, final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException {
+
+ try {
+ final Catalog catalog = catalogService.getFullCatalog(context);
+ final BillingAlignment alignment = catalog.billingAlignment(planPhaseSpecifier, effectiveDate);
+ return BillCycleDayCalculator.calculateBcdForAlignment(subscription, baseSubscription, alignment, accountTimeZone, accountBillCycleDayLocal);
+ } catch (final CatalogApiException e) {
+ throw new SubscriptionBaseApiException(e);
+ }
+ }
private DateTime getEffectiveDateForNewBCD(final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) {
if (internalCallContext.getAccountRecordId() == null) {