killbill-aplcache

invoice: Refactor the computation of next invoice future notitications

9/2/2015 12:22:21 AM

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
index 513cd87..51f2845 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
@@ -30,7 +30,9 @@ public class BillingIntervalDetail {
     private final int billingCycleDay;
     private final BillingPeriod billingPeriod;
     private final BillingMode billingMode;
+    // First date after the startDate aligned with the BCD
     private LocalDate firstBillingCycleDate;
+    // Date up to which we should bill
     private LocalDate effectiveEndDate;
     private LocalDate lastBillingCycleDate;
 
@@ -67,6 +69,14 @@ public class BillingIntervalDetail {
         return lastBillingCycleDate;
     }
 
+    public LocalDate getNextBillingCycleDate() {
+        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+        final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plusMonths(numberOfMonthsInPeriod) : firstBillingCycleDate;
+        final LocalDate nextBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
+        return nextBillingCycleDate;
+    }
+
+
     public boolean hasSomethingToBill() {
         return effectiveEndDate != null /* IN_ARREAR mode prior we have reached our firstBillingCycleDate */ &&
                (endDate == null || endDate.isAfter(startDate)); /* When there is an endDate, it should be > startDate since we don't bill for less than a day */
@@ -171,8 +181,9 @@ public class BillingIntervalDetail {
 
     private void calculateLastBillingCycleDate() {
 
-        if (effectiveEndDate == null) {
-            lastBillingCycleDate = firstBillingCycleDate;
+        // IN_ARREAR cases
+        if (effectiveEndDate == null || effectiveEndDate.compareTo(firstBillingCycleDate) < 0 ) {
+            lastBillingCycleDate = null;
             return;
         }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 070348d..3d8fecc 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -52,6 +52,7 @@ import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.InvalidDateSequenceException;
 import org.killbill.billing.invoice.model.RecurringInvoiceItem;
 import org.killbill.billing.invoice.model.RecurringInvoiceItemData;
+import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate;
 import org.killbill.billing.invoice.tree.AccountItemTree;
 import org.killbill.billing.invoice.usage.RawUsageOptimizer;
 import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult;
@@ -65,6 +66,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -123,8 +126,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                                                                    final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates,
                                                                    final InternalCallContext internalCallContext) throws InvoiceApiException {
 
-        final Map<String, LocalDate> perSubscriptionUsage = new HashMap<String, LocalDate>();
-
         final Map<UUID, List<InvoiceItem>> perSubscriptionConsumableInArrearUsageItems = extractPerSubscriptionExistingConsumableInArrearUsageItems(eventSet.getUsages(), existingInvoices);
         try {
             final List<InvoiceItem> items = Lists.newArrayList();
@@ -163,10 +164,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                     final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate());
                     final List<InvoiceItem> consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId);
 
-                    final List<InvoiceItem> newProposedItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
-                    items.addAll(newProposedItems);
-                    updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newProposedItems, perSubscriptionFutureNotificationDates);
-
+                    final List<InvoiceItem> newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
+                    items.addAll(newInArrearUsageItems);
+                    updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
                     curEvents = Lists.newArrayList();
                 }
                 curSubscriptionId = subscriptionId;
@@ -176,9 +176,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate());
                 final List<InvoiceItem> consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId);
 
-                final List<InvoiceItem> newProposedItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
-                items.addAll(newProposedItems);
-                updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newProposedItems, perSubscriptionFutureNotificationDates);
+                final List<InvoiceItem> newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
+                items.addAll(newInArrearUsageItems);
+                updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
             }
             return items;
 
@@ -188,14 +188,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     }
 
 
-    private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final List<InvoiceItem> newProposedItems, Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
-        for (final InvoiceItem item : newProposedItems) {
+    private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final List<InvoiceItem> newInArrearUsageItems, final BillingMode usageBillingMode, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
+        for (final InvoiceItem item : newInArrearUsageItems) {
             SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId);
             if (subscriptionFutureNotificationDates == null) {
-                subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates();
+                subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null);
                 perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates);
             }
-            subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), item.getEndDate());
+            subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), usageBillingMode, item.getEndDate());
         }
     }
 
@@ -285,7 +285,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     private List<InvoiceItem> processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events,
                                                             final LocalDate targetDate, final Currency currency, final List<InvoiceItem> proposedItems,
-                                                            Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate) throws InvoiceApiException {
+                                                            final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate) throws InvoiceApiException {
 
         if (events.size() == 0) {
             return proposedItems;
@@ -305,37 +305,18 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             if (!events.getSubscriptionIdsWithAutoInvoiceOff().
                     contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off
                 final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null;
-                final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode());
+                final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate);
                 proposedItems.addAll(newProposedItems);
-                updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), newProposedItems, perSubscriptionFutureNotificationDate);
             }
         }
-        final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode());
+        final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, logStringBuilder, events.getRecurringBillingMode(), perSubscriptionFutureNotificationDate);
         proposedItems.addAll(newProposedItems);
-        updatePerSubscriptionNextNotificationDate(nextEvent.getSubscription().getId(), newProposedItems, perSubscriptionFutureNotificationDate);
 
         log.info(logStringBuilder.toString());
 
         return proposedItems;
     }
 
-    private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final List<InvoiceItem> newProposedItems, Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
-        for (final InvoiceItem item : newProposedItems) {
-            if ((item.getEndDate() != null) &&
-                (item.getAmount() == null ||
-                 item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
-                SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId);
-                if (subscriptionFutureNotificationDates == null) {
-                    subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates();
-                    perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates);
-                }
-                subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(item.getEndDate());
-            }
-        }
-    }
-
-
-
 
     private List<InvoiceItem> processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List<InvoiceItem> proposedItems) {
         final Iterator<BillingEvent> eventIt = events.iterator();
@@ -353,7 +334,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day)
     private List<InvoiceItem> processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent,
                                                     final LocalDate targetDate, final Currency currency,
-                                                    final StringBuilder logStringBuilder, final BillingMode billingMode) throws InvoiceApiException {
+                                                    final StringBuilder logStringBuilder, final BillingMode billingMode,
+                                                    final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate) throws InvoiceApiException {
         final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
 
         // Handle recurring items
@@ -366,14 +348,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
                 final int billCycleDayLocal = thisEvent.getBillCycleDayLocal();
 
-                final List<RecurringInvoiceItemData> itemData;
+                final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate;
                 try {
-                    itemData = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode);
+                    itemDataWithNextBillingCycleDate = billingModeGenerator.generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode);
                 } catch (InvalidDateSequenceException e) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
                 }
 
-                for (final RecurringInvoiceItemData itemDatum : itemData) {
+                for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) {
                     final BigDecimal rate = thisEvent.getRecurringPrice();
 
                     if (rate != null) {
@@ -390,6 +372,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                         items.add(recurringItem);
                     }
                 }
+                updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, thisEvent.getBillingMode(), perSubscriptionFutureNotificationDate);
+
             }
         }
 
@@ -400,11 +384,45 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             logStringBuilder.append("\n\t")
                             .append(item);
         }
-
         return items;
     }
 
-    InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
+    private void updatePerSubscriptionNextNotificationDate(final UUID subscriptionId, final LocalDate nextBillingCycleDate, final List<InvoiceItem> newProposedItems, final BillingMode billingMode, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
+
+        LocalDate nextNotificationDate = null;
+        switch(billingMode) {
+            case IN_ADVANCE:
+                for (final InvoiceItem item : newProposedItems) {
+                    if ((item.getEndDate() != null) &&
+                        (item.getAmount() == null ||
+                         item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+                        if (nextNotificationDate == null) {
+                            nextNotificationDate = item.getEndDate();
+                        } else {
+                            nextNotificationDate = nextNotificationDate.compareTo(item.getEndDate()) > 0 ? nextNotificationDate : item.getEndDate();
+                        }
+                    }
+                }
+                break;
+            case IN_ARREAR:
+                nextNotificationDate = nextBillingCycleDate;
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized billing mode " + billingMode);
+        }
+        if (nextNotificationDate != null) {
+            SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId);
+            if (subscriptionFutureNotificationDates == null) {
+                subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(billingMode);
+                perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates);
+            }
+            subscriptionFutureNotificationDates.updateNextRecurringDateIfRequired(nextNotificationDate);
+
+        }
+    }
+
+
+    private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
                                        final LocalDate targetDate, final Currency currency) {
         final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone());
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
index a319245..0e0f30e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
@@ -18,25 +18,80 @@
 package org.killbill.billing.invoice.generator;
 
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 
 public class InvoiceWithMetadata {
 
     private final Invoice invoice;
     private final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates;
 
+
+    public InvoiceWithMetadata(final Invoice invoice, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
+        this.invoice = invoice;
+        this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates;
+        build();
+    }
+
+    public Invoice getInvoice() {
+        return invoice;
+    }
+
+    public Map<UUID, SubscriptionFutureNotificationDates> getPerSubscriptionFutureNotificationDates() {
+        return perSubscriptionFutureNotificationDates;
+    }
+
+    // Remove all the IN_ADVANCE items for which we have no invoice items
+    private void build() {
+        for (final UUID subscriptionId : perSubscriptionFutureNotificationDates.keySet()) {
+            final SubscriptionFutureNotificationDates tmp = perSubscriptionFutureNotificationDates.get(subscriptionId);
+            if (tmp.getRecurringBillingMode() == BillingMode.IN_ADVANCE && !hasItemsForSubscription(subscriptionId, InvoiceItemType.RECURRING)) {
+                tmp.resetNextRecurringDate();
+            }
+            if (tmp.getNextUsageDates() != null) {
+                final Iterator<UsageDef> it = tmp.getNextUsageDates().keySet().iterator();
+                while (it.hasNext()) {
+                    final UsageDef usageDef = it.next();
+                    if (usageDef.getBillingMode() == BillingMode.IN_ADVANCE && !hasItemsForSubscription(subscriptionId, InvoiceItemType.USAGE)) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean hasItemsForSubscription(final UUID subscriptionId, final InvoiceItemType invoiceItemType) {
+        return invoice != null && Iterables.any(invoice.getInvoiceItems(), new Predicate<InvoiceItem>() {
+            @Override
+            public boolean apply(final InvoiceItem input) {
+                return input.getInvoiceItemType() == invoiceItemType &&
+                       input.getSubscriptionId().equals(subscriptionId);
+            }
+        });
+    }
+
     public static class SubscriptionFutureNotificationDates {
 
+        private final BillingMode recurringBillingMode;
+
         private LocalDate nextRecurringDate;
-        private Map<String, LocalDate> nextUsageDates;
+        private Map<UsageDef, LocalDate> nextUsageDates;
 
-        public SubscriptionFutureNotificationDates() {
+        public SubscriptionFutureNotificationDates(final BillingMode recurringBillingMode) {
+            this.recurringBillingMode = recurringBillingMode;
             this.nextRecurringDate = null;
             this.nextUsageDates = null;
         }
@@ -45,22 +100,32 @@ public class InvoiceWithMetadata {
             nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate);
         }
 
-        public void updateNextUsageDateIfRequired(final String usageName, final LocalDate nextUsageDateCandidate) {
+        public void updateNextUsageDateIfRequired(final String usageName, final BillingMode billingMode, final LocalDate nextUsageDateCandidate) {
             if (nextUsageDates == null) {
-                nextUsageDates = new HashMap<String, LocalDate>();
+                nextUsageDates = new HashMap<UsageDef, LocalDate>();
             }
-            final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageName), nextUsageDateCandidate);
-            nextUsageDates.put(usageName, nextUsageDate);
+            final UsageDef usageDef = new UsageDef(usageName, billingMode);
+            final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageDef), nextUsageDateCandidate);
+            nextUsageDates.put(usageDef, nextUsageDate);
         }
 
         public LocalDate getNextRecurringDate() {
             return nextRecurringDate;
         }
 
-        public Map<String, LocalDate> getNextUsageDates() {
+        public Map<UsageDef, LocalDate> getNextUsageDates() {
             return nextUsageDates;
         }
 
+        public BillingMode getRecurringBillingMode() {
+            return recurringBillingMode;
+        }
+
+        public void resetNextRecurringDate() {
+            nextRecurringDate = null;
+        }
+
+
         private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, final LocalDate nextDateCandidate) {
             if (existingDate == null) {
                 return nextDateCandidate;
@@ -69,19 +134,49 @@ public class InvoiceWithMetadata {
             }
         }
 
-    }
 
-    public InvoiceWithMetadata(final Invoice invoice, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
-        this.invoice = invoice;
-        this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates;
-    }
+        public static class UsageDef {
 
-    public Invoice getInvoice() {
-        return invoice;
-    }
+            private final String usageName;
+            private final BillingMode billingMode;
 
-    public Map<UUID, SubscriptionFutureNotificationDates> getPerSubscriptionFutureNotificationDates() {
-        return perSubscriptionFutureNotificationDates;
-    }
+            public UsageDef(final String usageName, final BillingMode billingMode) {
+                this.usageName = usageName;
+                this.billingMode = billingMode;
+            }
+
+            public String getUsageName() {
+                return usageName;
+            }
+
+            public BillingMode getBillingMode() {
+                return billingMode;
+            }
+
+            @Override
+            public boolean equals(final Object o) {
+                if (this == o) {
+                    return true;
+                }
+                if (!(o instanceof UsageDef)) {
+                    return false;
+                }
+
+                final UsageDef usageDef = (UsageDef) o;
 
+                if (usageName != null ? !usageName.equals(usageDef.usageName) : usageDef.usageName != null) {
+                    return false;
+                }
+                return billingMode == usageDef.billingMode;
+
+            }
+
+            @Override
+            public int hashCode() {
+                int result = usageName != null ? usageName.hashCode() : 0;
+                result = 31 * result + (billingMode != null ? billingMode.hashCode() : 0);
+                return result;
+            }
+        }
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 6f4b247..58194a6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -70,6 +70,7 @@ import org.killbill.billing.invoice.generator.BillingIntervalDetail;
 import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.generator.InvoiceWithMetadata;
 import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.InvoiceItemFactory;
@@ -101,7 +102,6 @@ import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificatio
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
@@ -118,7 +118,7 @@ public class InvoiceDispatcher {
     private static final int NB_LOCK_TRY = 5;
 
     private static final Ordering<DateTime> UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural();
-
+    private final static Joiner JOINER_COMMA = Joiner.on(",");
     private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments();
 
     private final InvoiceGenerator generator;
@@ -358,9 +358,9 @@ public class InvoiceDispatcher {
             }
             // Add next usage dates if any
             if (subscriptionFutureNotificationDates.getNextUsageDates() != null) {
-                for (String usageName : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) {
-                    final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageName);
-                    final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageName, nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents);
+                for (UsageDef usageDef : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) {
+                    final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageDef);
+                    final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageDef.getUsageName(), nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents);
                     perSubscriptionNotifications.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true));
                 }
             }
@@ -409,14 +409,18 @@ public class InvoiceDispatcher {
     }
 
     private void logInvoiceWithItems(final Account account, final Invoice invoice, final LocalDate targetDate, final Set<UUID> adjustedUniqueOtherInvoiceId, final boolean isRealInvoiceWithItems) {
+        final StringBuilder tmp = new StringBuilder();
         if (isRealInvoiceWithItems) {
-            log.info("Generated invoice {} with {} items for accountId {} and targetDate {}", new Object[]{invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate});
+            tmp.append(String.format("Generated invoice %s with %d items for accountId %s and targetDate %s:\n", invoice.getId(), invoice.getNumberOfItems(), account.getId(), targetDate));
         } else {
-            final Joiner joiner = Joiner.on(",");
-            final String adjustedInvoices = joiner.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
-            log.info("Adjusting existing invoices {} with {} items for accountId {} and targetDate {})", new Object[]{adjustedInvoices, invoice.getNumberOfItems(),
-                                                                                                                      account.getId(), targetDate});
+            final String adjustedInvoices = JOINER_COMMA.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
+            tmp.append(String.format("Adjusting existing invoices %s with %d items for accountId %s and targetDate %s:\n",
+                                     adjustedInvoices, invoice.getNumberOfItems(), account.getId(), targetDate));
+        }
+        for (InvoiceItem item : invoice.getInvoiceItems()) {
+            tmp.append(String.format("\t item = %s\n", item));
         }
+        log.info(tmp.toString());
     }
 
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java
index 26fbfd9..19a8496 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/BillingModeGenerator.java
@@ -16,18 +16,14 @@
 
 package org.killbill.billing.invoice.model;
 
-import java.util.List;
-
 import javax.annotation.Nullable;
 
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 
 public interface BillingModeGenerator {
 
-    List<RecurringInvoiceItemData> generateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate,
-                                                           int billingCycleDay, BillingPeriod billingPeriod, BillingMode billingMode) throws InvalidDateSequenceException;
+    RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(LocalDate startDate, @Nullable LocalDate endDate, LocalDate targetDate,
+                                                                             int billingCycleDay, BillingPeriod billingPeriod, BillingMode billingMode) throws InvalidDateSequenceException;
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java
index 29bcc1f..2da9169 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultBillingModeGenerator.java
@@ -38,7 +38,7 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator {
     private static final Logger log = LoggerFactory.getLogger(DefaultBillingModeGenerator.class);
 
     @Override
-    public List<RecurringInvoiceItemData> generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate,
+    public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate,
                                                                   final LocalDate targetDate,
                                                                   final int billingCycleDayLocal,
                                                                   final BillingPeriod billingPeriod,
@@ -56,7 +56,7 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator {
 
         // We are not billing for less than a day
         if (!billingIntervalDetail.hasSomethingToBill()) {
-            return results;
+            return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode);
         }
         //
         // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do
@@ -66,7 +66,7 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator {
             final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod);
             final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods);
             results.add(itemData);
-            return results;
+            return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode);
         }
 
         //
@@ -126,6 +126,6 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator {
                 results.add(itemData);
             }
         }
-        return results;
+        return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode);
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java
new file mode 100644
index 0000000..3533240
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.model;
+
+import java.util.List;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.invoice.generator.BillingIntervalDetail;
+
+public class RecurringInvoiceItemDataWithNextBillingCycleDate {
+
+    private final List<RecurringInvoiceItemData> itemData;
+    private final BillingIntervalDetail billingIntervalDetail;
+    private final BillingMode billingMode;
+
+    public RecurringInvoiceItemDataWithNextBillingCycleDate(final List<RecurringInvoiceItemData> itemData, final BillingIntervalDetail billingIntervalDetail,final BillingMode billingMode) {
+        this.itemData = itemData;
+        this.billingIntervalDetail = billingIntervalDetail;
+        this.billingMode = billingMode;
+    }
+
+    public List<RecurringInvoiceItemData> getItemData() {
+        return itemData;
+    }
+
+    public LocalDate getNextBillingCycleDate() {
+        return billingIntervalDetail.getNextBillingCycleDate();
+    }
+}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java
new file mode 100644
index 0000000..009ee8c
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInArrearBillingIntervalDetail.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.generator;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestInArrearBillingIntervalDetail extends InvoiceTestSuiteNoDB {
+
+
+    /*
+     *           TD
+     * BCD      Start
+     * |---------|-----------------
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioBCDBeforeStart1() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-01-16");
+        final int bcd = 13;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertFalse(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-13"));
+        Assert.assertNull(billingIntervalDetail.getEffectiveEndDate());
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-13"));
+    }
+
+    /*
+     *
+     * BCD      Start       TD = (next BCD)
+     * |---------|----------|-------
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioBCDBeforeStart2() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-02-13");
+        final int bcd = 13;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-13"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-02-13"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-03-13"));
+    }
+
+
+    /*
+     * BCD
+     * Start     TD
+     * |---------|-----------------
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioBCDAEqualsStart1() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-01-19");
+        final int bcd = 16;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-16"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-16"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-16"));
+    }
+
+
+    /*
+     *
+     * Start     TD         BCD
+     * |---------|----------|-------
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioBCDAfterStart1() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-01-19");
+        final int bcd = 25;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertFalse(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-25"));
+        Assert.assertNull(billingIntervalDetail.getEffectiveEndDate());
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-01-25"));
+    }
+
+
+    /*
+     *                       TD
+     * Start    End          BCD
+     * |---------|------------|-------
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioBCDAfterStart2() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate end = new LocalDate("2012-01-19");
+        final LocalDate targetDate = new LocalDate("2012-01-25");
+        final int bcd = 25;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-25"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), end);
+        // STEPH maybe we should change because we actually don't want a notification
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-01-25"));
+    }
+
+
+    /*
+     *                     TD
+     * Start              BCD
+     * |--------------------|-------
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioBCDAfterStart3() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-01-25");
+        final int bcd = 25;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-25"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-25"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-25"));
+    }
+
+
+    /*
+     *
+     * Start   BCD   end   TD  next BCD
+     * |-------|------|----|------|---
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioEndDateBetweenPeriod1() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate end = new LocalDate("2012-01-20");
+        final LocalDate targetDate = new LocalDate("2012-01-25");
+        final int bcd = 18;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18"));
+    }
+
+    /*
+     *
+     * Start   BCD       TD  next BCD
+     * |-------|----------|------|---
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioEndDateBetweenPeriod2() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-01-25");
+        final int bcd = 18;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18"));
+    }
+
+    /*
+     *
+     * Start   BCD       TD    end   next BCD
+     * |-------|----------|----|------|---
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioEndDateBetweenPeriod3() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate end = new LocalDate("2012-01-28");
+        final LocalDate targetDate = new LocalDate("2012-01-25");
+        final int bcd = 18;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18"));
+    }
+
+    /*
+     *                              TD
+     * Start   BCD              next BCD
+     * |-------|--------------------|---
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioTargetDateOnNextBCD1() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate targetDate = new LocalDate("2012-02-18");
+        final int bcd = 18;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, null, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-02-18"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-03-18"));
+    }
+
+    /*
+     *                              TD
+     * Start   BCD          end    next BCD
+     * |-------|-------------|-------|---
+     *
+     */
+    @Test(groups = "fast")
+    public void testScenarioTargetDateOnNextBCD2() throws Exception {
+        final LocalDate start = new LocalDate("2012-01-16");
+        final LocalDate end = new LocalDate("2012-02-16");
+        final LocalDate targetDate = new LocalDate("2012-02-18");
+        final int bcd = 18;
+        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(start, end, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ARREAR);
+
+        Assert.assertTrue(billingIntervalDetail.hasSomethingToBill());
+        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-18"));
+        Assert.assertEquals(billingIntervalDetail.getEffectiveEndDate(), new LocalDate("2012-02-16"));
+        Assert.assertEquals(billingIntervalDetail.getNextBillingCycleDate(), new LocalDate("2012-02-18"));
+    }
+}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java
index db121f6..5766409 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInAdvanceBillingMode.java
@@ -151,8 +151,8 @@ public class TestInAdvanceBillingMode extends InvoiceTestSuiteNoDB {
                                     final LinkedHashMap<LocalDate, LocalDate> expectedDates) throws InvalidDateSequenceException {
         final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator();
 
-        final List<RecurringInvoiceItemData> invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE);
-
+        final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ADVANCE);
+        final List<RecurringInvoiceItemData> invoiceItems = invoiceItemsWithDates.getItemData();
         int i = 0;
         for (final LocalDate periodStartDate : expectedDates.keySet()) {
             Assert.assertEquals(invoiceItems.get(i).getStartDate(), periodStartDate);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java
index aa4733e..69a72aa 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestInArrearBillingMode.java
@@ -153,7 +153,8 @@ public class TestInArrearBillingMode extends InvoiceTestSuiteNoDB {
                                     final LinkedHashMap<LocalDate, LocalDate> expectedDates) throws InvalidDateSequenceException {
         final DefaultBillingModeGenerator billingMode = new DefaultBillingModeGenerator();
 
-        final List<RecurringInvoiceItemData> invoiceItems = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR);
+        final RecurringInvoiceItemDataWithNextBillingCycleDate invoiceItemsWithDates = billingMode.generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, BillingMode.IN_ARREAR);
+        final List<RecurringInvoiceItemData> invoiceItems = invoiceItemsWithDates.getItemData();
 
         int i = 0;
         for (final LocalDate periodStartDate : expectedDates.keySet()) {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java
index 82e110a..2a31e14 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/ProRationTestBase.java
@@ -30,6 +30,7 @@ import org.killbill.billing.invoice.model.BillingModeGenerator;
 import org.killbill.billing.invoice.model.DefaultBillingModeGenerator;
 import org.killbill.billing.invoice.model.InvalidDateSequenceException;
 import org.killbill.billing.invoice.model.RecurringInvoiceItemData;
+import org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.fail;
@@ -72,10 +73,10 @@ public abstract class ProRationTestBase extends InvoiceTestSuiteNoDB {
     }
 
     protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException {
-        final List<RecurringInvoiceItemData> items = getBillingModeGenerator().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode());
+        final RecurringInvoiceItemDataWithNextBillingCycleDate items = getBillingModeGenerator().generateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode());
 
         BigDecimal numberOfBillingCycles = ZERO;
-        for (final RecurringInvoiceItemData item : items) {
+        for (final RecurringInvoiceItemData item : items.getItemData()) {
             numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
         }
 
@@ -83,10 +84,10 @@ public abstract class ProRationTestBase extends InvoiceTestSuiteNoDB {
     }
 
     protected BigDecimal calculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate targetDate, final int billingCycleDay) throws InvalidDateSequenceException {
-        final List<RecurringInvoiceItemData> items = getBillingModeGenerator().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode());
+        final RecurringInvoiceItemDataWithNextBillingCycleDate items = getBillingModeGenerator().generateInvoiceItemData(startDate, null, targetDate, billingCycleDay, getBillingPeriod(), getBillingMode());
 
         BigDecimal numberOfBillingCycles = ZERO;
-        for (final RecurringInvoiceItemData item : items) {
+        for (final RecurringInvoiceItemData item : items.getItemData()) {
             numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
         }