killbill-memoizeit

entitlement, junction: Enhance Entitlement#getBillCycleDayLocal

6/11/2016 12:26:49 AM

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