diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index 7d74b2d..d43f6d1 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -37,7 +37,8 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.generator.InvoiceDateUtils;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
import com.google.common.collect.ImmutableList;
@@ -48,6 +49,79 @@ import static org.testng.Assert.assertTrue;
@Guice(modules = {BeatrixModule.class})
public class TestIntegration extends TestIntegrationBase {
@Test(groups = "slow")
+ public void testCancelBPWithAOTheSameDay() throws Exception {
+ // We take april as it has 30 days (easier to play with BCD)
+ final DateTime today = new DateTime(2012, 4, 1, 0, 0, 0, 0, testTimeZone);
+ final DateTime trialEndDate = new DateTime(2012, 5, 1, 0, 0, 0, 0, testTimeZone);
+ final Account account = createAccountWithPaymentMethod(getAccountData(1));
+
+ // Set clock to the initial start date
+ clock.setDeltaFromReality(today.getMillis() - clock.getUTCNow().getMillis());
+ final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+ final PlanPhaseSpecifier bpPlanPhaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
+ final SubscriptionData bpSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+ bpPlanPhaseSpecifier,
+ null,
+ context));
+ assertNotNull(bpSubscription);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ //
+ // ADD ADD_ON ON THE SAME DAY
+ //
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ final PlanPhaseSpecifier addonPlanPhaseSpecifier = new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+ final SubscriptionData aoSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(), addonPlanPhaseSpecifier, null, context));
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ //
+ // CANCEL BP ON THE SAME DAY (we should have two cancellations, BP and AO)
+ //
+ busHandler.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE);
+ bpSubscription.cancel(clock.getUTCNow(), false, context);
+ assertTrue(busHandler.isCompleted(DELAY));
+ assertListenerStatus();
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId());
+ assertEquals(invoices.size(), 3);
+ // The first invoice is for the trial BP
+ assertEquals(invoices.get(0).getNumberOfItems(), 1);
+ assertEquals(invoices.get(0).getInvoiceItems().get(0).getStartDate().compareTo(today), 0);
+ assertEquals(invoices.get(0).getInvoiceItems().get(0).getEndDate().compareTo(trialEndDate), 0);
+ // The second invoice should be adjusted for the AO (we paid for the full period)
+ assertEquals(invoices.get(1).getNumberOfItems(), 3);
+ for (final InvoiceItem item : invoices.get(1).getInvoiceItems()) {
+ if (InvoiceItemType.RECURRING.equals(item.getInvoiceItemType())) {
+ assertEquals(item.getStartDate().compareTo(today), 0);
+ assertEquals(item.getEndDate().compareTo(trialEndDate), 0);
+ assertEquals(item.getAmount().compareTo(new BigDecimal("399.9500")), 0);
+ } else if (InvoiceItemType.REPAIR_ADJ.equals(item.getInvoiceItemType())) {
+ assertEquals(item.getStartDate().compareTo(today), 0);
+ assertEquals(item.getEndDate().compareTo(trialEndDate), 0);
+ assertEquals(item.getAmount().compareTo(new BigDecimal("-399.9500")), 0);
+ } else {
+ assertEquals(item.getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
+ assertEquals(item.getStartDate().compareTo(today), 0);
+ assertEquals(item.getEndDate().compareTo(today), 0);
+ assertEquals(item.getAmount().compareTo(new BigDecimal("399.9500")), 0);
+ }
+ }
+ // Null invoice
+ assertEquals(invoices.get(2).getNumberOfItems(), 0);
+ }
+
+ @Test(groups = "slow")
public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
final LinkedHashMap<DateTime, List<NextEvent>> expectedStates = new LinkedHashMap<DateTime, List<NextEvent>>();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 7d34da2..4264af1 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -146,7 +146,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
for (final UUID invoiceId : amountOwedByInvoice.keySet()) {
final BigDecimal invoiceBalance = amountOwedByInvoice.get(invoiceId);
if (invoiceBalance.compareTo(BigDecimal.ZERO) < 0) {
- proposedItems.add(new CreditBalanceAdjInvoiceItem(invoiceId, accountId, clock.getUTCNow(), invoiceBalance.negate(), currency));
+ final DateTime creditDate = InvoiceDateUtils.roundDateTimeToDate(clock.getUTCNow(), DateTimeZone.UTC);
+ final CreditBalanceAdjInvoiceItem creditInvoiceItem = new CreditBalanceAdjInvoiceItem(invoiceId, accountId, creditDate, invoiceBalance.negate(), currency);
+ proposedItems.add(creditInvoiceItem);
}
}
}
@@ -156,7 +158,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
if (existingItem.getInvoiceItemType() == InvoiceItemType.RECURRING ||
existingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
final BigDecimal amountNegated = existingItem.getAmount() == null ? null : existingItem.getAmount().negate();
- RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(existingItem.getInvoiceId(), existingItem.getAccountId(), existingItem.getStartDate(),existingItem.getEndDate(), amountNegated, existingItem.getCurrency(), existingItem.getId());
+ final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(existingItem.getInvoiceId(), existingItem.getAccountId(), existingItem.getStartDate(),existingItem.getEndDate(), amountNegated, existingItem.getCurrency(), existingItem.getId());
proposedItems.add(repairItem);
}
}
@@ -192,7 +194,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
if (creditAmount.compareTo(BigDecimal.ZERO) < 0) {
- proposedItems.add(new CreditBalanceAdjInvoiceItem(invoiceId, accountId, clock.getUTCNow(), creditAmount, targetCurrency));
+ final DateTime creditDate = InvoiceDateUtils.roundDateTimeToDate(clock.getUTCNow(), DateTimeZone.UTC);
+ final CreditBalanceAdjInvoiceItem creditInvoiceItem = new CreditBalanceAdjInvoiceItem(invoiceId, accountId, creditDate, creditAmount, targetCurrency);
+ proposedItems.add(creditInvoiceItem);
}
}
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index ce98f24..2538869 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -86,30 +86,35 @@ public class BillCycleDayCalculator {
break;
case BUNDLE:
final Subscription baseSub = entitlementApi.getBaseSubscription(bundle.getId());
- final Plan basePlan = baseSub.getCurrentPlan();
+ Plan basePlan = baseSub.getCurrentPlan();
+ if (basePlan == null) {
+ // The BP has been cancelled
+ final EffectiveSubscriptionEvent previousTransition = baseSub.getPreviousTransition();
+ basePlan = catalog.findPlan(previousTransition.getPreviousPlan(), previousTransition.getEffectiveTransitionTime(), previousTransition.getSubscriptionStartDate());
+ }
result = calculateBcdFromSubscription(baseSub, basePlan, account);
break;
case SUBSCRIPTION:
result = calculateBcdFromSubscription(subscription, plan, account);
break;
}
+
if (result == -1) {
throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
}
- return result;
+ return result;
}
private int calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account) throws AccountApiException {
final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate());
// There are really two kind of billCycleDay:
// - a System billingCycleDay which should be computed from UTC time (in order to get the correct notification time at
- // the end of each service period
+ // the end of each service period)
// - a User billingCycleDay which should align with the account timezone
//
- // TODO At this point we only compute the system one; should we need two filds in the account table
+ // TODO At this point we only compute the system one; should we need two fields in the account table
//return date.toDateTime(account.getTimeZone()).getDayOfMonth();
return date.getDayOfMonth();
}
-
}