killbill-aplcache

#458 - Moving childAccount notifications back to OverdueListener.

7/14/2016 2:57:03 PM

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java
index 8f5b7af..fba90a7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueChildParentRelationship.java
@@ -18,6 +18,8 @@
 package org.killbill.billing.beatrix.integration.overdue;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -35,6 +37,7 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.testng.annotations.Test;
+import org.weakref.jmx.internal.guava.collect.Iterables;
 
 import static org.testng.Assert.assertEquals;
 
@@ -165,8 +168,7 @@ public class TestOverdueChildParentRelationship extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
         assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(new BigDecimal("249.95")), 0);
 
-        allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(false);
-        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount.getId());
+        allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(1, 1, childAccount);
 
         // check invoice generated after clear child account
         invoiceChecker.checkInvoice(childAccount.getId(), 3, callContext,
@@ -178,11 +180,105 @@ public class TestOverdueChildParentRelationship extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(childAccount.getId(), callContext).compareTo(BigDecimal.valueOf(-66.65)), 0);
     }
 
-    private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final boolean extraPayment) {
+    @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD for Parent and Child accounts")
+    public void testOverdueStagesParentMultiChildAccounts() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        setupAccount();
+        final Account childAccount1 = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+        final Account childAccount2 = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+        final Account childAccount3 = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), true));
+        final Account childAccountNoPaymentDelegated = createAccountWithNonOsgiPaymentMethod(getChildAccountData(0, account.getId(), false));
+
+        // Set next invoice to fail and create subscription
+        paymentPlugin.makeAllInvoicesFailWithError(true);
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(childAccount1.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        bundle = subscriptionApi.getSubscriptionBundle(baseEntitlement.getBundleId(), callContext);
+
+        invoiceChecker.checkInvoice(childAccount1.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // 2012-05-2 => DAY 1 : Parent Invoice commit status
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE);
+
+        // 2012-05-2 => DAY 2
+        addDaysAndCheckForCompletion(1);
+        final DefaultEntitlement baseEntitlement2 = createBaseEntitlementAndCheckForCompletion(childAccount2.getId(), "externalKey2", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        invoiceChecker.checkInvoice(childAccount2.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement2.getId(), new LocalDate(2012, 5, 3), callContext);
+
+        // 2012-05-3 => DAY 3 : Parent Invoice commit status  (Sub.2)
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(27, NextEvent.PHASE, NextEvent.INVOICE);
+
+        // 2012-06-01 => Parent Invoice payment attempt
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+        // 2012-06-02 => DAY 30 have to get out of trial {I0, P0}  (Sub.2)
+        addDaysAndCheckForCompletion(1, NextEvent.PHASE, NextEvent.INVOICE);
+
+        // 2012-06-03 => Parent Invoice payment attempt  (Sub.2)
+        addDaysAndCheckForCompletion(1, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+
+        invoiceChecker.checkInvoice(childAccount1.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // 2012-06-09 => DAY 8 : Retry P0
+        addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, account.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount1.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount2.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        // 2012-06-11 => Day 10 - Retry P0 - Move to OD1 state
+        addDaysAndCheckForCompletion(2, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK,
+                                     NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD1", account.getId());
+        checkODState("OD1", childAccount1.getId());
+        checkODState("OD1", childAccount2.getId());
+        checkODState("OD1", childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        // 2012-06-17 => DAY 16 : Retry P0
+        addDaysAndCheckForCompletion(6, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD1", account.getId());
+        checkODState("OD1", childAccount1.getId());
+        checkODState("OD1", childAccount2.getId());
+        checkODState("OD1", childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        // 2012-06-19 => Day 18 - Retry P0 - Move to OD2 state
+        addDaysAndCheckForCompletion(2, NextEvent.TAG, NextEvent.BLOCK, NextEvent.TAG, NextEvent.BLOCK, NextEvent.TAG,
+                                     NextEvent.BLOCK, NextEvent.TAG, NextEvent.BLOCK,
+                                     NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
+        checkODState("OD2", account.getId());
+        checkODState("OD2", childAccount1.getId());
+        checkODState("OD2", childAccount2.getId());
+        checkODState("OD2", childAccount3.getId());
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccountNoPaymentDelegated.getId());
+
+        allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(0, 4, childAccount1, childAccount2, childAccount3);
+
+    }
+
+    private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final int expectedInvoicesCount, final int expectedNullInvoicesCount, final Account... childAccounts) {
 
         // Reset plugin so payments should now succeed
         paymentPlugin.makeAllInvoicesFailWithError(false);
 
+        // build expected event list
+        List<NextEvent> nextEventList = new ArrayList<NextEvent>();
+        nextEventList.addAll(Collections.nCopies(childAccounts.length + 1, NextEvent.BLOCK));
+        nextEventList.addAll(Collections.nCopies(childAccounts.length + 1, NextEvent.TAG));
+        nextEventList.addAll(Collections.nCopies(expectedInvoicesCount, NextEvent.INVOICE));
+        nextEventList.addAll(Collections.nCopies(expectedNullInvoicesCount, NextEvent.NULL_INVOICE));
+        nextEventList.addAll(Arrays.asList(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT));
+
         //
         // We now pay all unpaid invoices.
         //
@@ -198,15 +294,14 @@ public class TestOverdueChildParentRelationship extends TestOverdueBase {
                 if (remainingUnpaidInvoices > 0) {
                     createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
                 } else {
-                    if (extraPayment) {
-                        createPaymentAndCheckForCompletion(account, invoice, NextEvent.BLOCK, NextEvent.TAG, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-                    } else {
-                        createPaymentAndCheckForCompletion(account, invoice, NextEvent.BLOCK, NextEvent.TAG, NextEvent.BLOCK, NextEvent.TAG, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE, NextEvent.NULL_INVOICE); // , NextEvent.INVOICE, NextEvent.NULL_INVOICE
-                    }
+                    createPaymentAndCheckForCompletion(account, invoice, Iterables.toArray(nextEventList, NextEvent.class));
                 }
             }
         }
         checkODState(OverdueWrapper.CLEAR_STATE_NAME);
+        for (Account childAccount : childAccounts) {
+            checkODState(OverdueWrapper.CLEAR_STATE_NAME, childAccount.getId());
+        }
     }
 
     private List<Invoice> getUnpaidInvoicesOrderFromRecent() {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index 867b81f..a0abc46 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -18,11 +18,14 @@
 
 package org.killbill.billing.overdue.listener;
 
+import java.util.List;
 import java.util.UUID;
 
 import javax.inject.Named;
 
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.events.ControlTagCreationInternalEvent;
@@ -66,6 +69,7 @@ public class OverdueListener {
     private final OverduePoster asyncPoster;
     private final OverdueConfigCache overdueConfigCache;
     private final NonEntityDao nonEntityDao;
+    private final AccountInternalApi accountApi;
 
     @Inject
     public OverdueListener(final NonEntityDao nonEntityDao,
@@ -73,13 +77,15 @@ public class OverdueListener {
                            final Clock clock,
                            @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED)  final OverduePoster asyncPoster,
                            final OverdueConfigCache overdueConfigCache,
-                           final InternalCallContextFactory internalCallContextFactory) {
+                           final InternalCallContextFactory internalCallContextFactory,
+                           final AccountInternalApi accountApi) {
         this.nonEntityDao = nonEntityDao;
         this.clock = clock;
         this.asyncPoster = asyncPoster;
         this.overdueConfigCache = overdueConfigCache;
         this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.accountApi = accountApi;
     }
 
     @AllowConcurrentEvents
@@ -139,8 +145,26 @@ public class OverdueListener {
         final boolean shouldInsertNotification = shouldInsertNotification(callContext);
 
         if (shouldInsertNotification) {
-            final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
+            OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
             asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, callContext);
+
+            try {
+                final List<Account> childrenAccounts = accountApi.getChildrenAccounts(accountId, callContext);
+                if (childrenAccounts != null) {
+                    for (Account childAccount : childrenAccounts) {
+
+                        if (childAccount.isPaymentDelegatedToParent()) {
+                            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(childAccount.getId(), callContext);
+                            final InternalCallContext accountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), callContext);
+                            notificationKey = new OverdueAsyncBusNotificationKey(childAccount.getId(), action);
+                            asyncPoster.insertOverdueNotification(childAccount.getId(), clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, accountContext);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.error("Error loading child accounts from account " + accountId);
+            }
+
         }
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
index 2c24098..979e991 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
@@ -18,11 +18,7 @@
 
 package org.killbill.billing.overdue.wrapper;
 
-import java.util.List;
-
 import org.joda.time.DateTime;
-import org.killbill.billing.account.api.Account;
-import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
@@ -64,7 +60,6 @@ public class OverdueWrapper {
     private final BillingStateCalculator billingStateCalcuator;
     private final OverdueStateApplicator overdueStateApplicator;
     private final InternalCallContextFactory internalCallContextFactory;
-    private final AccountInternalApi accountApi;
 
     public OverdueWrapper(final ImmutableAccountData overdueable,
                           final BlockingInternalApi api,
@@ -73,8 +68,7 @@ public class OverdueWrapper {
                           final Clock clock,
                           final BillingStateCalculator billingStateCalcuator,
                           final OverdueStateApplicator overdueStateApplicator,
-                          final InternalCallContextFactory internalCallContextFactory,
-                          final AccountInternalApi accountApi) {
+                          final InternalCallContextFactory internalCallContextFactory) {
         this.overdueable = overdueable;
         this.overdueStateSet = overdueStateSet;
         this.api = api;
@@ -83,7 +77,6 @@ public class OverdueWrapper {
         this.billingStateCalcuator = billingStateCalcuator;
         this.overdueStateApplicator = overdueStateApplicator;
         this.internalCallContextFactory = internalCallContextFactory;
-        this.accountApi = accountApi;
     }
 
     public OverdueState refresh(final DateTime effectiveDate, final InternalCallContext context) throws OverdueException, OverdueApiException {
@@ -114,33 +107,6 @@ public class OverdueWrapper {
         final OverdueState nextOverdueState = overdueStateSet.calculateOverdueState(billingState, clock.getToday(billingState.getAccountTimeZone()));
 
         overdueStateApplicator.apply(effectiveDate, overdueStateSet, billingState, overdueable, currentOverdueState, nextOverdueState, context);
-
-        try {
-            final List<Account> childrenAccounts = accountApi.getChildrenAccounts(overdueable.getId(), context);
-            if (childrenAccounts != null) {
-                for (Account account : childrenAccounts) {
-
-                    if (account.isPaymentDelegatedToParent()) {
-                        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(account.getId(), context);
-                        final InternalCallContext accountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), context);
-
-                        final ImmutableAccountData accountData = accountApi.getImmutableAccountDataById(account.getId(), accountContext);
-                        final BillingState childBillingState = new BillingState(accountData.getId(),
-                                                                                billingState.getNumberOfUnpaidInvoices(),
-                                                                                billingState.getBalanceOfUnpaidInvoices(),
-                                                                                billingState.getDateOfEarliestUnpaidInvoice(),
-                                                                                accountData.getTimeZone(),
-                                                                                billingState.getIdOfEarliestUnpaidInvoice(),
-                                                                                billingState.getResponseForLastFailedPayment(),
-                                                                                billingState.getTags());
-                        overdueStateApplicator.apply(effectiveDate, overdueStateSet, childBillingState, accountData, currentOverdueState, nextOverdueState, accountContext);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            log.error("Error loading child accounts from account " + overdueable.getId());
-        }
-
         return nextOverdueState;
     }
 
@@ -163,29 +129,14 @@ public class OverdueWrapper {
         final BlockingState blockingStateForService = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context);
         final String previousOverdueStateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
         final OverdueState previousOverdueState = overdueStateSet.findState(previousOverdueStateName);
-
-        // TODO maguero: should we do the same as "refreshWithLock"?
-        overdueStateApplicator.clear(effectiveDate, overdueable, previousOverdueState, overdueStateSet.getClearState(), context);
-
-        try {
-            final List<Account> childrenAccounts = accountApi.getChildrenAccounts(overdueable.getId(), context);
-            if (childrenAccounts != null) {
-                for (Account account : childrenAccounts) {
-                    if (account.isPaymentDelegatedToParent()) {
-                        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(account.getId(), context);
-                        final InternalCallContext accountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), context);
-
-                        final ImmutableAccountData accountData = accountApi.getImmutableAccountDataById(account.getId(), accountContext);
-                        overdueStateApplicator.clear(effectiveDate, accountData, previousOverdueState, overdueStateSet.getClearState(), accountContext);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            log.error("Error loading child accounts from account " + overdueable.getId());
-        }
     }
 
-    public BillingState billingState(final InternalTenantContext context) throws OverdueException {
+    public BillingState billingState(final InternalCallContext context) throws OverdueException {
+        if ((overdueable.getParentAccountId() != null) && (overdueable.isPaymentDelegatedToParent())) {
+            final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(overdueable.getParentAccountId(), context);
+            final InternalCallContext parentAccountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), context);
+            return billingStateCalcuator.calculateBillingState(overdueable, parentAccountContext);
+        }
         return billingStateCalcuator.calculateBillingState(overdueable, context);
     }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
index 19cd3e5..aa43f69 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -77,13 +77,13 @@ public class OverdueWrapperFactory {
     }
 
     public OverdueWrapper createOverdueWrapperFor(final ImmutableAccountData blockable, final InternalTenantContext context) throws OverdueException {
-        return new OverdueWrapper(blockable, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator, internalCallContextFactory, accountApi);
+        return new OverdueWrapper(blockable, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator, internalCallContextFactory);
     }
 
     public OverdueWrapper createOverdueWrapperFor(final UUID id, final InternalTenantContext context) throws OverdueException {
         try {
             final ImmutableAccountData account = accountApi.getImmutableAccountDataById(id, context);
-            return new OverdueWrapper(account, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator, internalCallContextFactory, accountApi);
+            return new OverdueWrapper(account, api, getOverdueStateSet(context), locker, clock, billingStateCalculator, overdueStateApplicator, internalCallContextFactory);
         } catch (final AccountApiException e) {
             throw new OverdueException(e);
         }