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/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);
}