killbill-memoizeit

Details

diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
index 7485e19..705b7e7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
@@ -49,7 +49,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
     protected final InternalCallContextFactory internalCallContextFactory;
     protected final Clock clock;
     protected final EntitlementState state;
-    protected final BlockingState entitlementBlockingState;
+    protected final LocalDate effectiveEndDate;
     protected final BlockingChecker checker;
     protected final UUID accountId;
     protected final String externalKey;
@@ -58,7 +58,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
     protected final BlockingStateDao blockingStateDao;
 
     public DefaultEntitlement(final EntitlementDateHelper dateHelper, final SubscriptionBase subscriptionBase, final UUID accountId,
-                              final String externalKey, final EntitlementState state, final BlockingState entitlementBlockingState, final DateTimeZone accountTimeZone,
+                              final String externalKey, final EntitlementState state, final LocalDate effectiveEndDate, final DateTimeZone accountTimeZone,
                               final EntitlementApi entitlementApi, final InternalCallContextFactory internalCallContextFactory,
                               final BlockingStateDao blockingStateDao,
                               final Clock clock, final BlockingChecker checker) {
@@ -68,8 +68,8 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         this.accountId = accountId;
         this.externalKey = externalKey;
         this.state = state;
+        this.effectiveEndDate = effectiveEndDate;
         this.entitlementApi = entitlementApi;
-        this.entitlementBlockingState = entitlementBlockingState;
         this.accountTimeZone = accountTimeZone;
         this.internalCallContextFactory = internalCallContextFactory;
         this.clock = clock;
@@ -83,7 +83,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
              in.getAccountId(),
              in.getExternalKey(),
              in.getState(),
-             in.getEntitlementBlockingState(),
+             in.getEffectiveEndDate(),
              in.getAccountTimeZone(),
              in.getEntitlementApi(),
              in.getInternalCallContextFactory(),
@@ -91,7 +91,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
              in.getClock(), in.getChecker());
     }
 
-    // STEPH_ENT should be remove but beatrix tests need to be changed
     public SubscriptionBase getSubscriptionBase() {
         return subscriptionBase;
     }
@@ -112,10 +111,6 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         return clock;
     }
 
-    public BlockingState getEntitlementBlockingState() {
-        return entitlementBlockingState;
-    }
-
     public BlockingChecker getChecker() {
         return checker;
     }
@@ -165,17 +160,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
     @Override
     public LocalDate getEffectiveEndDate() {
-        if (entitlementBlockingState != null && entitlementBlockingState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CANCELLED)) {
-            return new LocalDate(entitlementBlockingState.getEffectiveDate(), accountTimeZone);
-        }
-        return null;
-        //return subscriptionBase.getEndDate() != null ? new LocalDate(subscriptionBase.getEndDate(), accountTimeZone) : null;
-    }
-
-    @Override
-    public LocalDate getRequestedEndDate() {
-        // STEPH_ENT
-        return null; //subscriptionBase.;
+        return effectiveEndDate;
     }
 
     @Override
@@ -333,15 +318,4 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
             throw new EntitlementApiException(e);
         }
     }
-
-    @Override
-    public Entitlement block(final String serviceName, final LocalDate effectiveDate, final CallContext context) throws EntitlementApiException {
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
-    }
-
-    @Override
-    public Entitlement unblock(final String serviceName, final LocalDate effectiveDate, final CallContext context) throws EntitlementApiException {
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
-    }
-
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
index f9d0366..ba0121c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlementApi.java
@@ -46,6 +46,8 @@ import com.ning.billing.entitlement.block.BlockingChecker;
 import com.ning.billing.entitlement.block.BlockingChecker.BlockingAggregator;
 import com.ning.billing.entitlement.dao.BlockingStateDao;
 import com.ning.billing.subscription.api.SubscriptionBase;
+import com.ning.billing.subscription.api.transfer.SubscriptionBaseTransferApi;
+import com.ning.billing.subscription.api.transfer.SubscriptionBaseTransferApiException;
 import com.ning.billing.subscription.api.user.SubscriptionBaseApiException;
 import com.ning.billing.subscription.api.user.SubscriptionBaseBundle;
 import com.ning.billing.util.callcontext.CallContext;
@@ -70,6 +72,7 @@ public class DefaultEntitlementApi implements EntitlementApi {
     public static final String ENT_STATE_CANCELLED = "ENT_CANCELLED";
 
     private final SubscriptionBaseInternalApi subscriptionInternalApi;
+    private final SubscriptionBaseTransferApi subscriptionTransferApi;
     private final AccountInternalApi accountApi;
     private final Clock clock;
     private final InternalCallContextFactory internalCallContextFactory;
@@ -79,10 +82,11 @@ public class DefaultEntitlementApi implements EntitlementApi {
     private final PersistentBus eventBus;
 
     @Inject
-    public DefaultEntitlementApi(PersistentBus eventBus, final InternalCallContextFactory internalCallContextFactory, final SubscriptionBaseInternalApi subscriptionInternalApi, final AccountInternalApi accountApi, final BlockingStateDao blockingStateDao, final Clock clock, final BlockingChecker checker) {
+    public DefaultEntitlementApi(final PersistentBus eventBus, final InternalCallContextFactory internalCallContextFactory, final SubscriptionBaseTransferApi subscriptionTransferApi, final SubscriptionBaseInternalApi subscriptionInternalApi, final AccountInternalApi accountApi, final BlockingStateDao blockingStateDao, final Clock clock, final BlockingChecker checker) {
         this.eventBus = eventBus;
         this.internalCallContextFactory = internalCallContextFactory;
         this.subscriptionInternalApi = subscriptionInternalApi;
+        this.subscriptionTransferApi = subscriptionTransferApi;
         this.accountApi = accountApi;
         this.clock = clock;
         this.checker = checker;
@@ -122,9 +126,9 @@ public class DefaultEntitlementApi implements EntitlementApi {
             final BlockingState currentBaseState = blockingStateDao.getBlockingStateForService(baseSubscription.getId(), EntitlementService.ENTITLEMENT_SERVICE_NAME, contextWithValidAccountRecordId);
 
             final Account account = accountApi.getAccountById(bundle.getAccountId(), context);
-
+            final LocalDate baseEntitlementEffectiveEndDate = getEffectiveEndDate(bundle.getAccountId(), baseSubscription, account.getTimeZone(), contextWithValidAccountRecordId);
             // Check if there is a BP and if it is active
-            final EntitlementState baseEntitlementState = getStateForEntitlement(baseSubscription, currentBaseState, account.getTimeZone(), contextWithValidAccountRecordId);
+            final EntitlementState baseEntitlementState = getStateForEntitlement(baseEntitlementEffectiveEndDate, baseSubscription, account.getTimeZone(), contextWithValidAccountRecordId);
             if (baseSubscription.getCategory() != ProductCategory.BASE ||
                 baseEntitlementState != EntitlementState.ACTIVE) {
                 throw new EntitlementApiException(ErrorCode.SUB_GET_NO_SUCH_BASE_SUBSCRIPTION, baseSubscription.getBundleId());
@@ -139,7 +143,8 @@ public class DefaultEntitlementApi implements EntitlementApi {
 
             final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
             final SubscriptionBase subscription = subscriptionInternalApi.createSubscription(baseSubscription.getBundleId(), planPhaseSpecifier, requestedDate, context);
-            return new DefaultEntitlement(dateHelper, subscription, bundle.getAccountId(), bundle.getExternalKey(), getStateForEntitlement(subscription, currentBaseState, account.getTimeZone(), context), null, account.getTimeZone(),
+
+            return new DefaultEntitlement(dateHelper, subscription, bundle.getAccountId(), bundle.getExternalKey(), baseEntitlementState, null, account.getTimeZone(),
                                           this, internalCallContextFactory, blockingStateDao, clock, checker);
         } catch (SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
@@ -176,7 +181,12 @@ public class DefaultEntitlementApi implements EntitlementApi {
             final Account account = accountApi.getAccountById(bundle.getAccountId(), context);
             final BlockingState currentState = blockingStateDao.getBlockingStateForService(subscription.getId(), EntitlementService.ENTITLEMENT_SERVICE_NAME, context);
 
-            return new DefaultEntitlement(dateHelper, subscription, bundle.getAccountId(), bundle.getExternalKey(), getStateForEntitlement(subscription, currentState, account.getTimeZone(), context), currentState, account.getTimeZone(),
+
+            final LocalDate entitlementEffectiveEndDate = getEffectiveEndDate(bundle.getAccountId(), subscription, account.getTimeZone(), context);
+            final EntitlementState entitlementState = getStateForEntitlement(entitlementEffectiveEndDate, subscription, account.getTimeZone(), context);
+
+
+            return new DefaultEntitlement(dateHelper, subscription, bundle.getAccountId(), bundle.getExternalKey(), entitlementState, entitlementEffectiveEndDate, account.getTimeZone(),
                                           this, internalCallContextFactory, blockingStateDao, clock, checker);
         } catch (SubscriptionBaseApiException e) {
             throw new EntitlementApiException(e);
@@ -231,11 +241,12 @@ public class DefaultEntitlementApi implements EntitlementApi {
                 @Override
                 public Entitlement apply(@Nullable final SubscriptionBase input) {
 
-                    final BlockingState currentState = blockingStateDao.getBlockingStateForService(input.getId(), EntitlementService.ENTITLEMENT_SERVICE_NAME, context);
+                    final LocalDate effectiveEndDate = getEffectiveEndDate(accountId, input, account.getTimeZone(), context);
+                    final EntitlementState entitlementState = getStateForEntitlement(effectiveEndDate, input, account.getTimeZone(), context);
 
                     return new DefaultEntitlement(dateHelper, input, accountId, externalKey,
-                                                  getStateForEntitlement(input, currentState, account.getTimeZone(), context),
-                                                  currentState,
+                                                  entitlementState,
+                                                  effectiveEndDate,
                                                   account.getTimeZone(),
                                                   thisEntitlementApi,
                                                   internalCallContextFactory, blockingStateDao, clock, checker);
@@ -247,12 +258,34 @@ public class DefaultEntitlementApi implements EntitlementApi {
     }
 
 
-    private EntitlementState getStateForEntitlement(final SubscriptionBase subscriptionBase, final BlockingState currentState, final DateTimeZone accountTimeZone, final InternalTenantContext context) {
+    private LocalDate getEffectiveEndDate(final UUID accountId, final SubscriptionBase subscriptionBase, final DateTimeZone accountTimeZone, final InternalTenantContext context) {
+
+        LocalDate result = null;
+
+        final BlockingState subEntitlementState = blockingStateDao.getBlockingStateForService(subscriptionBase.getId(), EntitlementService.ENTITLEMENT_SERVICE_NAME, context);
+        if (subEntitlementState != null && subEntitlementState.getStateName().equals(ENT_STATE_CANCELLED)) {
+            result = new LocalDate(subEntitlementState.getEffectiveDate(), accountTimeZone);
+        }
+
+        final BlockingState bundleEntitlementState = blockingStateDao.getBlockingStateForService(subscriptionBase.getBundleId(), EntitlementService.ENTITLEMENT_SERVICE_NAME, context);
+        if (bundleEntitlementState != null && bundleEntitlementState .getStateName().equals(ENT_STATE_CANCELLED)) {
+            final LocalDate localDate = new LocalDate(bundleEntitlementState.getEffectiveDate(), accountTimeZone);
+            result = result == null || result.compareTo(localDate) < 0 ? localDate : result;
+        }
+
+        final BlockingState accountEntitlementState = blockingStateDao.getBlockingStateForService(accountId, EntitlementService.ENTITLEMENT_SERVICE_NAME, context);
+        if (accountEntitlementState != null && accountEntitlementState .getStateName().equals(ENT_STATE_CANCELLED)) {
+            final LocalDate localDate = new LocalDate(accountEntitlementState.getEffectiveDate(), accountTimeZone);
+            result = result == null || result.compareTo(localDate) < 0 ? localDate : result;
+        }
+        return result;
+    }
+
+    private EntitlementState getStateForEntitlement(final LocalDate entitlementEndDate, final SubscriptionBase subscriptionBase, final DateTimeZone accountTimeZone, final InternalTenantContext context) {
 
         // Current state for the ENTITLEMENT_SERVICE_NAME is set to cancelled
-        if (currentState != null &&
-            currentState.getStateName().equals(ENT_STATE_CANCELLED) &&
-            dateHelper.isBeforeOrEqualsToday(currentState.getEffectiveDate(), accountTimeZone)) {
+        if (entitlementEndDate != null &&
+            entitlementEndDate.compareTo(new LocalDate(clock.getUTCNow(), accountTimeZone)) <= 0) {
             return EntitlementState.CANCELLED;
         }
 
@@ -355,13 +388,38 @@ public class DefaultEntitlementApi implements EntitlementApi {
 
     @Override
     public UUID transferEntitlements(final UUID sourceAccountId, final UUID destAccountId, final String externalKey, final LocalDate effectiveDate, final CallContext context) throws EntitlementApiException {
-        return null;
+        return transferEntitlementsOverrideBillingPolicy(sourceAccountId, destAccountId, externalKey, effectiveDate, BillingActionPolicy.IMMEDIATE, context);
     }
 
     @Override
     public UUID transferEntitlementsOverrideBillingPolicy(final UUID sourceAccountId, final UUID destAccountId, final String externalKey, final LocalDate effectiveDate, final BillingActionPolicy billingPolicy, final CallContext context) throws EntitlementApiException {
-        return null;
-    }
+        final boolean cancelImm;
+        switch (billingPolicy) {
+            case IMMEDIATE:
+                cancelImm = true;
+                break;
+            case END_OF_TERM:
+                cancelImm = false;
+                break;
+            default:
+                throw new RuntimeException("Unexpected billing policy " + billingPolicy);
+        }
 
+        final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(sourceAccountId, context);
+        try {
+            final SubscriptionBaseBundle bundle = subscriptionInternalApi.getBundleForAccountAndKey(sourceAccountId, externalKey, contextWithValidAccountRecordId);
+            final SubscriptionBase baseSubscription = subscriptionInternalApi.getBaseSubscription(bundle.getId(), contextWithValidAccountRecordId);
+
+            final DateTime requestedDate = dateHelper.fromLocalDateAndReferenceTime(effectiveDate, baseSubscription.getStartDate(), contextWithValidAccountRecordId);
+            final SubscriptionBaseBundle newBundle = subscriptionTransferApi.transferBundle(sourceAccountId, destAccountId, externalKey, requestedDate, true, cancelImm, context);
 
+            blockingStateDao.setBlockingState(new DefaultBlockingState(bundle.getId(), BlockingStateType.BUNDLE, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, requestedDate), clock, contextWithValidAccountRecordId);
+
+            return newBundle.getId();
+        } catch (SubscriptionBaseTransferApiException e) {
+            throw new EntitlementApiException(e);
+        } catch (SubscriptionBaseApiException e) {
+            throw new EntitlementApiException(e);
+        }
+    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
index 617e7d6..e41d2da 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -3,7 +3,9 @@ package com.ning.billing.entitlement.api;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.List;
+import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -11,6 +13,8 @@ import org.testng.annotations.Test;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.catalog.api.BillingActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
@@ -19,8 +23,11 @@ import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementSourceType;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
 
+import com.google.common.io.BaseEncoding;
+
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
 
 
 public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedDB {
@@ -252,4 +259,57 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
             Assert.fail("Test failed " + e.getMessage());
         }
     }
-}
+
+
+
+    @Test(groups = "slow")
+    public void testTransferBundle() {
+
+        try {
+
+            final LocalDate initialDate = new LocalDate(2013, 8, 7);
+            clock.setDay(initialDate);
+
+            final Account accountSrc = accountApi.createAccount(getAccountData(7), callContext);
+
+            final Account accountDesc = accountApi.createAccount(getAccountData(15), callContext);
+
+            final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+            // Create entitlement
+            final Entitlement baseEntitlement = entitlementApi.createBaseEntitlement(accountSrc.getId(), spec, accountSrc.getExternalKey(), initialDate, callContext);
+
+            final DateTime ctd = clock.getUTCNow().plusDays(30).plusMonths(1);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            clock.addDays(32);
+            // Set manually since no invoice
+            subscriptionInternalApi.setChargedThroughDate(baseEntitlement.getId(), ctd, internalCallContext);
+            assertTrue(testListener.isCompleted(5000));
+
+            // Transfer bundle to dest acccount
+            final LocalDate effectiveDate = new LocalDate(clock.getUTCNow(), accountSrc.getTimeZone());
+            testListener.pushExpectedEvent(NextEvent.TRANSFER);
+            final UUID newBundleId = entitlementApi.transferEntitlementsOverrideBillingPolicy(accountSrc.getId(), accountDesc.getId(), baseEntitlement.getExternalKey(), effectiveDate, BillingActionPolicy.END_OF_TERM, callContext);
+            assertTrue(testListener.isCompleted(5000));
+
+            final Entitlement oldBaseEntitlement = entitlementApi.getAllEntitlementsForAccountIdAndExternalKey(accountSrc.getId(), accountSrc.getExternalKey(), callContext).get(0);
+            assertEquals(oldBaseEntitlement.getEffectiveEndDate(), effectiveDate);
+            assertEquals(oldBaseEntitlement.getState(), EntitlementState.CANCELLED);
+
+            final List<Entitlement> entitlements = entitlementApi.getAllEntitlementsForBundle(newBundleId, callContext);
+            assertEquals(entitlements.size(), 1);
+
+            final Entitlement newBaseEntitlement = entitlements.get(0);
+            assertEquals(newBaseEntitlement.getState(), EntitlementState.ACTIVE);
+            assertEquals(newBaseEntitlement.getEffectiveStartDate(), effectiveDate);
+            assertEquals(newBaseEntitlement.getEffectiveEndDate(), null);
+
+        } catch (AccountApiException e) {
+            Assert.fail("Test failed " + e.getMessage());
+        } catch (EntitlementApiException e) {
+            Assert.fail("Test failed " + e.getMessage());
+        }
+    }
+
+
+        }
diff --git a/subscription/src/main/java/com/ning/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java b/subscription/src/main/java/com/ning/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
index 4dea370..ef05793 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/api/transfer/DefaultSubscriptionBaseTransferApi.java
@@ -271,7 +271,7 @@ public class DefaultSubscriptionBaseTransferApi extends SubscriptionApiBase impl
             // Atomically cancel all subscription on old account and create new bundle, subscriptions, events for new account
             dao.transfer(sourceAccountId, destAccountId, bundleMigrationData, transferCancelDataList, fromInternalCallContext, toInternalCallContext);
 
-            return bundle;
+            return bundleMigrationData.getData();
         } catch (SubscriptionBaseRepairException e) {
             throw new SubscriptionBaseTransferApiException(e);
         }
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockSubscriptionModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockSubscriptionModule.java
index 79ac9cf..9e5c5fc 100644
--- a/util/src/test/java/com/ning/billing/mock/glue/MockSubscriptionModule.java
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockSubscriptionModule.java
@@ -51,6 +51,7 @@ public class MockSubscriptionModule extends AbstractModule implements Subscripti
         installSubscriptionMigrationApi();
         installSubscriptionInternalApi();
         installSubscriptionTimelineApi();
+        installSubscriptionTransferApi();
     }
 
     @Override