killbill-memoizeit
Changes
invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java 39(+20 -19)
invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java 4(+1 -3)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java 49(+47 -2)
invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java 49(+46 -3)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java 18(+11 -7)
Details
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 b823655..8fdc8bc 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
@@ -57,6 +57,7 @@ import org.killbill.billing.invoice.tree.AccountItemTree;
import org.killbill.billing.invoice.usage.RawUsageOptimizer;
import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult;
import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear;
+import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.SubscriptionConsumableInArrearItemsAndNextNotificationDate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.util.config.InvoiceConfig;
@@ -66,8 +67,6 @@ 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;
@@ -164,9 +163,10 @@ 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> newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
+ final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
+ final List<InvoiceItem> newInArrearUsageItems = subscriptionResult.getInvoiceItems();
items.addAll(newInArrearUsageItems);
- updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
+ updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
curEvents = Lists.newArrayList();
}
curSubscriptionId = subscriptionId;
@@ -176,9 +176,10 @@ 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> newInArrearUsageItems = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
+ final SubscriptionConsumableInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of());
+ final List<InvoiceItem> newInArrearUsageItems = subscriptionResult.getInvoiceItems();
items.addAll(newInArrearUsageItems);
- updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, newInArrearUsageItems, BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
+ updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
}
return items;
@@ -187,19 +188,21 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
}
+ private void updatePerSubscriptionNextNotificationUsageDate(final UUID subscriptionId, final Map<String, LocalDate> nextBillingCycleDates, final BillingMode usageBillingMode, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
+ if (usageBillingMode == BillingMode.IN_ADVANCE) {
+ throw new IllegalStateException("Not implemented Yet)");
+ }
- 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(null);
- perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates);
- }
- subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(item.getUsageName(), usageBillingMode, item.getEndDate());
+ SubscriptionFutureNotificationDates subscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates.get(subscriptionId);
+ if (subscriptionFutureNotificationDates == null) {
+ subscriptionFutureNotificationDates = new SubscriptionFutureNotificationDates(null);
+ perSubscriptionFutureNotificationDates.put(subscriptionId, subscriptionFutureNotificationDates);
+ }
+ for (final String usageName : nextBillingCycleDates.keySet()) {
+ subscriptionFutureNotificationDates.updateNextUsageDateIfRequired(usageName, usageBillingMode, nextBillingCycleDates.get(usageName));
}
}
-
private Map<UUID, List<InvoiceItem>> extractPerSubscriptionExistingConsumableInArrearUsageItems(final Map<String, Usage> knownUsage, @Nullable final List<Invoice> existingInvoices) {
if (existingInvoices == null || existingInvoices.isEmpty()) {
@@ -317,7 +320,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
return proposedItems;
}
-
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();
while (eventIt.hasNext()) {
@@ -390,7 +392,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
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) {
+ switch (billingMode) {
case IN_ADVANCE:
for (final InvoiceItem item : newProposedItems) {
if ((item.getEndDate() != null) &&
@@ -421,9 +423,8 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
}
-
private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
- final LocalDate targetDate, final Currency currency) {
+ final LocalDate targetDate, final Currency currency) {
final LocalDate roundedStartDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone());
if (roundedStartDate.isAfter(targetDate)) {
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 0e0f30e..50f41da 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,7 +18,6 @@
package org.killbill.billing.invoice.generator;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
@@ -29,7 +28,6 @@ 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;
@@ -39,7 +37,6 @@ 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;
@@ -56,20 +53,14 @@ public class InvoiceWithMetadata {
// Remove all the IN_ADVANCE items for which we have no invoice items
private void build() {
+ // nextRecurringDate are computed based on *proposed* items, and not missing items (= proposed - existing). So
+ // we need to filter out the dates for which there is no item left otherwsie we may end up in creating too many notification dates
+ // and in particular that could lead to an infinite loop.
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();
- }
- }
- }
}
}
@@ -97,16 +88,20 @@ public class InvoiceWithMetadata {
}
public void updateNextRecurringDateIfRequired(final LocalDate nextRecurringDateCandidate) {
- nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate);
+ if (nextRecurringDateCandidate != null) {
+ nextRecurringDate = getMaxDate(nextRecurringDate, nextRecurringDateCandidate);
+ }
}
public void updateNextUsageDateIfRequired(final String usageName, final BillingMode billingMode, final LocalDate nextUsageDateCandidate) {
- if (nextUsageDates == null) {
- nextUsageDates = new HashMap<UsageDef, LocalDate>();
+ if (nextUsageDateCandidate != null) {
+ if (nextUsageDates == null) {
+ nextUsageDates = new HashMap<UsageDef, LocalDate>();
+ }
+ final UsageDef usageDef = new UsageDef(usageName, billingMode);
+ final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageDef), nextUsageDateCandidate);
+ nextUsageDates.put(usageDef, nextUsageDate);
}
- final UsageDef usageDef = new UsageDef(usageName, billingMode);
- final LocalDate nextUsageDate = getMaxDate(nextUsageDates.get(usageDef), nextUsageDateCandidate);
- nextUsageDates.put(usageDef, nextUsageDate);
}
public LocalDate getNextRecurringDate() {
@@ -125,7 +120,6 @@ public class InvoiceWithMetadata {
nextRecurringDate = null;
}
-
private static LocalDate getMaxDate(@Nullable final LocalDate existingDate, final LocalDate nextDateCandidate) {
if (existingDate == null) {
return nextDateCandidate;
@@ -134,7 +128,6 @@ public class InvoiceWithMetadata {
}
}
-
public static class UsageDef {
private final String usageName;
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 58194a6..6dabd5c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -39,12 +39,10 @@ import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingActionPolicy;
-import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
-import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
@@ -66,7 +64,6 @@ import org.killbill.billing.invoice.api.user.DefaultNullInvoiceEvent;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
-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;
@@ -77,7 +74,6 @@ import org.killbill.billing.invoice.model.InvoiceItemFactory;
import org.killbill.billing.invoice.model.RecurringInvoiceItem;
import org.killbill.billing.invoice.notification.DefaultNextBillingDateNotifier;
import org.killbill.billing.invoice.notification.NextBillingDateNotificationKey;
-import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.junction.BillingInternalApi;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
@@ -272,7 +268,7 @@ public class InvoiceDispatcher {
final Invoice invoice = invoiceWithMetadata.getInvoice();
// Compute future notifications
- final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, billingEvents, dateAndTimeZoneContext, context);
+ final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, dateAndTimeZoneContext, context);
//
@@ -343,7 +339,7 @@ public class InvoiceDispatcher {
}
- private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) {
+ private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) {
final Map<UUID, List<SubscriptionNotification>> result = new HashMap<UUID, List<SubscriptionNotification>>();
@@ -360,7 +356,7 @@ public class InvoiceDispatcher {
if (subscriptionFutureNotificationDates.getNextUsageDates() != null) {
for (UsageDef usageDef : subscriptionFutureNotificationDates.getNextUsageDates().keySet()) {
final LocalDate nextNotificationDateForUsage = subscriptionFutureNotificationDates.getNextUsageDates().get(usageDef);
- final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageDef.getUsageName(), nextNotificationDateForUsage, dateAndTimeZoneContext, billingEvents);
+ final DateTime subscriptionUsageCallbackDate = nextNotificationDateForUsage != null ? dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(nextNotificationDateForUsage) : null;
perSubscriptionNotifications.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true));
}
}
@@ -496,20 +492,6 @@ public class InvoiceDispatcher {
return internalCallContextFactory.createCallContext(context);
}
- private DateTime getNextUsageBillingDate(final UUID subscriptionId, final String usageName, final LocalDate chargedThroughDate, final DateAndTimeZoneContext dateAndTimeZoneContext, final BillingEventSet billingEvents) {
-
- final Usage usage = billingEvents.getUsages().get(usageName);
- final BillingEvent billingEventSubscription = Iterables.tryFind(billingEvents, new Predicate<BillingEvent>() {
- @Override
- public boolean apply(@Nullable final BillingEvent input) {
- return input.getSubscription().getId().equals(subscriptionId);
- }
- }).orNull();
-
- final LocalDate nextCallbackUsageDate = (usage.getBillingMode() == BillingMode.IN_ARREAR) ? BillingIntervalDetail.alignProposedBillCycleDate(chargedThroughDate.plusMonths(usage.getBillingPeriod().getNumberOfMonths()), billingEventSubscription.getBillCycleDayLocal()) : chargedThroughDate;
- return dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(nextCallbackUsageDate);
- }
-
private void setChargedThroughDates(final DateAndTimeZoneContext dateAndTimeZoneContext,
final Collection<InvoiceItem> fixedPriceItems,
final Collection<InvoiceItem> recurringItems,
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 2da9169..db75fef 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
@@ -56,7 +56,7 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator {
// We are not billing for less than a day
if (!billingIntervalDetail.hasSomethingToBill()) {
- return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode);
+ return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail);
}
//
// 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 new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode);
+ return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail);
}
//
@@ -126,6 +126,6 @@ public class DefaultBillingModeGenerator implements BillingModeGenerator {
results.add(itemData);
}
}
- return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail, billingMode);
+ return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail);
}
}
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
index 3533240..97e1b67 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItemDataWithNextBillingCycleDate.java
@@ -27,12 +27,10 @@ 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) {
+ public RecurringInvoiceItemDataWithNextBillingCycleDate(final List<RecurringInvoiceItemData> itemData, final BillingIntervalDetail billingIntervalDetail) {
this.itemData = itemData;
this.billingIntervalDetail = billingIntervalDetail;
- this.billingMode = billingMode;
}
public List<RecurringInvoiceItemData> getItemData() {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
index a7d78e4..5cbb223 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.TieredBlock;
@@ -127,6 +128,25 @@ public class ContiguousIntervalConsumableInArrear {
return this;
}
+ public class ConsumableInArrearItemsAndNextNotificationDate {
+ private final List<InvoiceItem> invoiceItems;
+ private final LocalDate nextNotificationDate;
+
+ public ConsumableInArrearItemsAndNextNotificationDate(final List<InvoiceItem> invoiceItems, final LocalDate nextNotificationDate) {
+ this.invoiceItems = invoiceItems;
+ this.nextNotificationDate = nextNotificationDate;
+ }
+
+ public List<InvoiceItem> getInvoiceItems() {
+ return invoiceItems;
+ }
+
+ public LocalDate getNextNotificationDate() {
+ return nextNotificationDate;
+ }
+ }
+
+
/**
* Compute the missing usage invoice items based on what should be billed and what has been billed ($ amount comparison).
*
@@ -134,12 +154,12 @@ public class ContiguousIntervalConsumableInArrear {
* @return
* @throws CatalogApiException
*/
- public List<InvoiceItem> computeMissingItems(final List<InvoiceItem> existingUsage) throws CatalogApiException {
+ public ConsumableInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException {
Preconditions.checkState(isBuilt.get());
if (transitionTimes.size() < 2) {
- return ImmutableList.of();
+ return new ConsumableInArrearItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of(), null);
}
final List<InvoiceItem> result = Lists.newLinkedList();
@@ -184,9 +204,34 @@ public class ContiguousIntervalConsumableInArrear {
}
}
}
+
+ final LocalDate nextNotificationdate = computeNextNotificationDate();
+ return new ConsumableInArrearItemsAndNextNotificationDate(result, nextNotificationdate);
+ }
+
+ private LocalDate computeNextNotificationDate() {
+ LocalDate result = null;
+ final Iterator<BillingEvent> eventIt = billingEvents.iterator();
+ BillingEvent nextEvent = eventIt.next();
+ while (eventIt.hasNext()) {
+ final BillingEvent thisEvent = nextEvent;
+ nextEvent = eventIt.next();
+ final LocalDate startDate = new LocalDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone());
+ final LocalDate endDate = new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone());
+
+ final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, endDate, targetDate, thisEvent.getBillCycleDayLocal(), usage.getBillingPeriod(), BillingMode.IN_ARREAR);
+ final LocalDate nextBillingCycleDate = bid.getNextBillingCycleDate();
+ result = (result == null || result.compareTo(nextBillingCycleDate) < 0) ? nextBillingCycleDate : result;
+ }
+
+ final LocalDate startDate = new LocalDate(nextEvent.getEffectiveDate(), nextEvent.getTimeZone());
+ final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, null, targetDate, nextEvent.getBillCycleDayLocal(), usage.getBillingPeriod(), BillingMode.IN_ARREAR);
+ final LocalDate nextBillingCycleDate = bid.getNextBillingCycleDate();
+ result = (result == null || result.compareTo(nextBillingCycleDate) < 0) ? nextBillingCycleDate : result;
return result;
}
+
@VisibleForTesting
List<RolledUpUsage> getRolledUpUsage() {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
index 66f7449..e2b6965 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
@@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -31,6 +32,7 @@ import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.catalog.api.UsageType;
import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.usage.ContiguousIntervalConsumableInArrear.ConsumableInArrearItemsAndNextNotificationDate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
@@ -38,6 +40,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@@ -84,6 +88,7 @@ public class SubscriptionConsumableInArrear {
}));
}
+
/**
* Based on billing events, (@code existingUsage} and targetDate, figure out what remains to be billed.
*
@@ -91,16 +96,18 @@ public class SubscriptionConsumableInArrear {
* @return
* @throws CatalogApiException
*/
- public List<InvoiceItem> computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage) throws CatalogApiException {
+ public SubscriptionConsumableInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage) throws CatalogApiException {
- final List<InvoiceItem> result = Lists.newLinkedList();
+ final SubscriptionConsumableInArrearItemsAndNextNotificationDate result = new SubscriptionConsumableInArrearItemsAndNextNotificationDate();
final List<ContiguousIntervalConsumableInArrear> billingEventTransitionTimePeriods = computeInArrearUsageInterval();
for (ContiguousIntervalConsumableInArrear usageInterval : billingEventTransitionTimePeriods) {
- result.addAll(usageInterval.computeMissingItems(existingUsage));
+ result.addConsumableInArrearItemsAndNextNotificationDate(usageInterval.getUsage().getName(), usageInterval.computeMissingItemsAndNextNotificationDate(existingUsage));
}
return result;
}
+
+
@VisibleForTesting
List<ContiguousIntervalConsumableInArrear> computeInArrearUsageInterval() {
@@ -169,4 +176,40 @@ public class SubscriptionConsumableInArrear {
}
return result;
}
+
+ public class SubscriptionConsumableInArrearItemsAndNextNotificationDate {
+ private List<InvoiceItem> invoiceItems;
+ private Map<String, LocalDate> perUsageNotificationDates;
+
+ public SubscriptionConsumableInArrearItemsAndNextNotificationDate() {
+ this.invoiceItems = null;
+ this.perUsageNotificationDates = null;
+ }
+
+ public void addConsumableInArrearItemsAndNextNotificationDate(final String usageName, final ConsumableInArrearItemsAndNextNotificationDate input) {
+ if (!input.getInvoiceItems().isEmpty()) {
+ if (invoiceItems == null) {
+ invoiceItems = new LinkedList<InvoiceItem>();
+ }
+ invoiceItems.addAll(input.getInvoiceItems());
+
+ }
+
+ if (input.getNextNotificationDate() != null) {
+ if (perUsageNotificationDates == null) {
+ perUsageNotificationDates = new HashMap<String, LocalDate>();
+ }
+ perUsageNotificationDates.put(usageName, input.getNextNotificationDate());
+ }
+ }
+
+ public List<InvoiceItem> getInvoiceItems() {
+ return invoiceItems != null ? invoiceItems : ImmutableList.<InvoiceItem>of();
+ }
+
+ public Map<String, LocalDate> getPerUsageNotificationDates() {
+ return perUsageNotificationDates != null ? perUsageNotificationDates : ImmutableMap.<String, LocalDate>of();
+ }
+ }
+
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index c59589f..dd025c0 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -35,6 +35,7 @@ import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.ContiguousIntervalConsumableInArrear.ConsumableInArrearItemsAndNextNotificationDate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUsage;
@@ -88,6 +89,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = startDate.plusDays(1);
final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
@@ -127,6 +129,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
@@ -156,8 +159,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = endDate;
- final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.<Usage>emptyList());
- final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.<Usage>emptyList());
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2);
@@ -168,7 +171,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
invoiceItems.add(ii2);
- final List<InvoiceItem> rawResults = intervalConsumableInArrear.computeMissingItems(invoiceItems);
+ final ConsumableInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
+ final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
assertEquals(rawResults.size(), 4);
final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
@@ -216,16 +220,16 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate t0 = new LocalDate(2015, 03, BCD);
- final BillingEvent eventT0 = createMockBillingEvent(t0.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.<Usage>emptyList());
+ final BillingEvent eventT0 = createMockBillingEvent(t0.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate t1 = new LocalDate(2015, 04, BCD);
- final BillingEvent eventT1 = createMockBillingEvent(t1.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.<Usage>emptyList());
+ final BillingEvent eventT1 = createMockBillingEvent(t1.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate t2 = new LocalDate(2015, 05, BCD);
- final BillingEvent eventT2 = createMockBillingEvent(t2.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.<Usage>emptyList());
+ final BillingEvent eventT2 = createMockBillingEvent(t2.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate t3 = new LocalDate(2015, 06, BCD);
- final BillingEvent eventT3 = createMockBillingEvent(t3.toDateTimeAtStartOfDay(DateTimeZone.UTC), Collections.<Usage>emptyList());
+ final BillingEvent eventT3 = createMockBillingEvent(t3.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate targetDate = t3;
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
index 7434bd8..a2d90d9 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
@@ -60,15 +60,15 @@ public class TestSubscriptionConsumableInArrear extends TestUsageInArrearBase {
final Usage usage2 = createDefaultUsage(usageName2, BillingPeriod.MONTHLY, tier2);
final DateTime dt1 = new DateTime(2013, 3, 23, 4, 34, 59, DateTimeZone.UTC);
- final BillingEvent evt1 = createMockBillingEvent(dt1, ImmutableList.<Usage>builder().add(usage1).add(usage2).build());
+ final BillingEvent evt1 = createMockBillingEvent(dt1, BillingPeriod.MONTHLY, ImmutableList.<Usage>builder().add(usage1).add(usage2).build());
billingEvents.add(evt1);
final DateTime dt2 = new DateTime(2013, 4, 23, 4, 34, 59, DateTimeZone.UTC);
- final BillingEvent evt2 = createMockBillingEvent(dt2, ImmutableList.<Usage>builder().add(usage1).build());
+ final BillingEvent evt2 = createMockBillingEvent(dt2, BillingPeriod.MONTHLY, ImmutableList.<Usage>builder().add(usage1).build());
billingEvents.add(evt2);
final DateTime dt3 = new DateTime(2013, 5, 23, 4, 34, 59, DateTimeZone.UTC);
- final BillingEvent evt3 = createMockBillingEvent(dt3, ImmutableList.<Usage>builder().add(usage1).add(usage2).build());
+ final BillingEvent evt3 = createMockBillingEvent(dt3, BillingPeriod.MONTHLY, ImmutableList.<Usage>builder().add(usage1).add(usage2).build());
billingEvents.add(evt3);
LocalDate targetDate = new LocalDate(2013, 6, 23);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
index 6a61d18..616e852 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
@@ -109,12 +109,14 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
return block;
}
- protected BillingEvent createMockBillingEvent(DateTime effectiveDate, final List<Usage> usages) {
+ protected BillingEvent createMockBillingEvent(DateTime effectiveDate, BillingPeriod billingPeriod, final List<Usage> usages) {
final BillingEvent result = Mockito.mock(BillingEvent.class);
Mockito.when(result.getCurrency()).thenReturn(Currency.BTC);
Mockito.when(result.getBillCycleDayLocal()).thenReturn(BCD);
Mockito.when(result.getTimeZone()).thenReturn(DateTimeZone.UTC);
Mockito.when(result.getEffectiveDate()).thenReturn(effectiveDate);
+ Mockito.when(result.getBillingPeriod()).thenReturn(billingPeriod);
+
final Account account = Mockito.mock(Account.class);
Mockito.when(account.getId()).thenReturn(accountId);
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
index cf02a82..b2e8ce6 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBlockingCalculator.java
@@ -28,7 +28,6 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.BillingMode;
import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;