killbill-aplcache
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 19(+19 -0)
invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java 26(+13 -13)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java 17(+13 -4)
invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java 6(+4 -2)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java 4(+4 -0)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index d7896b0..1323c31 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -94,6 +94,25 @@ public class TestConsumableInArrear extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+
+ // Should be ignored because this is outside of optimization range (org.killbill.invoice.readMaxRawUsagePreviousPeriod = 2) => we will only look for items > 2012-7-1 - 2 months = 2012-5-1
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 30), 100L, callContext);
+
+ // Should be invoiced from past period
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 1), 199L, callContext);
+
+ // New usage for this past period
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 1), 50L, callContext);
+ setUsage(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 16), 300L, callContext);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+ clock.setDay(new LocalDate(2012, 8, 1));
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 8, 1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+
}
private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) {
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 61c3063..1bc6c58 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
@@ -53,6 +53,7 @@ import org.killbill.billing.invoice.model.RecurringInvoiceItem;
import org.killbill.billing.invoice.model.RecurringInvoiceItemData;
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.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
@@ -123,8 +124,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final List<InvoiceItem> items = Lists.newArrayList();
final Iterator<BillingEvent> events = eventSet.iterator();
- boolean seenAnyUsageItems = false;
- List<RawUsage> rawUsage = ImmutableList.of();
+ RawUsageOptimizerResult rawUsageOptimizerResult = null;
List<BillingEvent> curEvents = Lists.newArrayList();
UUID curSubscriptionId = null;
while (events.hasNext()) {
@@ -136,24 +136,25 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
// Optimize to do the usage query only once after we know there are indeed some usage items
- if (!seenAnyUsageItems) {
- final boolean foundUsage = Iterables.tryFind(event.getUsages(), new Predicate<Usage>() {
+ if (rawUsageOptimizerResult == null &&
+ Iterables.any(event.getUsages(), new Predicate<Usage>() {
@Override
public boolean apply(@Nullable final Usage input) {
return (input.getUsageType() == UsageType.CONSUMABLE &&
input.getBillingMode() == BillingMode.IN_ARREAR);
}
- }).orNull() != null;
- if (foundUsage) {
- rawUsage = rawUsageOptimizer.getConsumableInArrearUsage(new LocalDate(event.getEffectiveDate(), account.getTimeZone()), targetDate, Iterables.concat(perSubscriptionConsumableInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext);
- seenAnyUsageItems = true;
- }
+ })) {
+ rawUsageOptimizerResult = rawUsageOptimizer.getConsumableInArrearUsage(new LocalDate(event.getEffectiveDate(), account.getTimeZone()), targetDate, Iterables.concat(perSubscriptionConsumableInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext);
+ }
+
+ // None of the billing events report any usage (CONSUMABLE/IN_ARREAR) sections
+ if (rawUsageOptimizerResult == null) {
+ continue;
}
- // We always go in the usage invoicing logic to at least generate $0 USAGE items that will indicate the time for the next notification
final UUID subscriptionId = event.getSubscription().getId();
if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) {
- final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsage, targetDate);
+ final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate());
final List<InvoiceItem> consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId);
items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of()));
curEvents = Lists.newArrayList();
@@ -162,8 +163,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
curEvents.add(event);
}
if (curSubscriptionId != null) {
-
- final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsage, targetDate);
+ final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate());
final List<InvoiceItem> consumableInUsageArrearItems = perSubscriptionConsumableInArrearUsageItems.get(curSubscriptionId);
items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(consumableInUsageArrearItems != null ? consumableInUsageArrearItems : ImmutableList.<InvoiceItem>of()));
}
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 2c051fb..f1747c2 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
@@ -70,13 +70,15 @@ public class ContiguousIntervalConsumableInArrear {
private final LocalDate targetDate;
private final UUID invoiceId;
private final AtomicBoolean isBuilt;
+ private final LocalDate rawUsageStartDate;
- public ContiguousIntervalConsumableInArrear(final Usage usage, final UUID invoiceId, final List<RawUsage> rawSubscriptionUsage, final LocalDate targetDate) {
+ public ContiguousIntervalConsumableInArrear(final Usage usage, final UUID invoiceId, final List<RawUsage> rawSubscriptionUsage, final LocalDate targetDate, final LocalDate rawUsageStartDate) {
this.usage = usage;
this.invoiceId = invoiceId;
this.unitTypes = getConsumableInArrearUnitTypes(usage);
this.rawSubscriptionUsage = rawSubscriptionUsage;
this.targetDate = targetDate;
+ this.rawUsageStartDate = rawUsageStartDate;
this.billingEvents = Lists.newLinkedList();
this.transitionTimes = Lists.newLinkedList();
this.isBuilt = new AtomicBoolean(false);
@@ -109,10 +111,14 @@ public class ContiguousIntervalConsumableInArrear {
int numberOfPeriod = 0;
// First billingCycleDate prior startDate
LocalDate nextBillCycleDate = bid.getFutureBillingDateFor(numberOfPeriod);
- transitionTimes.add(startDate);
+ if (startDate.compareTo(rawUsageStartDate) >= 0) {
+ transitionTimes.add(startDate);
+ }
while (!nextBillCycleDate.isAfter(endDate)) {
if (nextBillCycleDate.isAfter(startDate)) {
- transitionTimes.add(nextBillCycleDate);
+ if (nextBillCycleDate.compareTo(rawUsageStartDate) >= 0) {
+ transitionTimes.add(nextBillCycleDate);
+ }
}
numberOfPeriod++;
nextBillCycleDate = bid.getFutureBillingDateFor(numberOfPeriod);
@@ -132,8 +138,11 @@ public class ContiguousIntervalConsumableInArrear {
Preconditions.checkState(isBuilt.get());
- final List<InvoiceItem> result = Lists.newLinkedList();
+ if (transitionTimes.size() < 2) {
+ return ImmutableList.of();
+ }
+ final List<InvoiceItem> result = Lists.newLinkedList();
final List<RolledUpUsage> allUsage = getRolledUpUsage();
// We start by generating 'marker' USAGE items with $0 that will allow to correctly insert the next notification for when there is no USAGE to bill.
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
index effa298..7ef5bff 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
@@ -67,9 +67,13 @@ public class RawUsageOptimizer {
this.config = config;
}
- public List<RawUsage> getConsumableInArrearUsage(final LocalDate firstEventStartDate, final LocalDate targetDate, final Iterable<InvoiceItem> existingUsageItems, final Map<String, Usage> knownUsage, final InternalCallContext internalCallContext) {
+ public RawUsageOptimizerResult getConsumableInArrearUsage(final LocalDate firstEventStartDate, final LocalDate targetDate, final Iterable<InvoiceItem> existingUsageItems, final Map<String, Usage> knownUsage, final InternalCallContext internalCallContext) {
final LocalDate targetStartDate = config.getMaxRawUsagePreviousPeriod() > 0 ? getOptimizedRawUsageStartDate(firstEventStartDate, targetDate, existingUsageItems, knownUsage) : firstEventStartDate;
- return usageApi.getRawUsageForAccount(targetStartDate, targetDate, internalCallContext);
+ log.info("RawUsageOptimizer [accountRecordId = {}]: rawUsageStartDate = {}, (proposed) firstEventStartDate = {}",
+ new Object[]{internalCallContext.getAccountRecordId(), targetStartDate, firstEventStartDate});
+
+ final List<RawUsage> rawUsageData = usageApi.getRawUsageForAccount(targetStartDate, targetDate, internalCallContext);
+ return new RawUsageOptimizerResult(firstEventStartDate, targetStartDate, rawUsageData);
}
@VisibleForTesting
@@ -108,12 +112,7 @@ public class RawUsageOptimizer {
if (perBillingPeriodMostRecentConsumableInArrearItemEndDate[usage.getBillingPeriod().ordinal()] == null) {
perBillingPeriodMostRecentConsumableInArrearItemEndDate[usage.getBillingPeriod().ordinal()] = item.getEndDate();
- if (!Iterables.any(ImmutableList.copyOf(perBillingPeriodMostRecentConsumableInArrearItemEndDate), new Predicate<LocalDate>() {
- @Override
- public boolean apply(@Nullable final LocalDate input) {
- return input == null;
- }
- })) {
+ if (!containsNullEntries(perBillingPeriodMostRecentConsumableInArrearItemEndDate)) {
break;
}
}
@@ -134,8 +133,43 @@ public class RawUsageOptimizer {
}
final LocalDate result = targetStartDate.compareTo(firstEventStartDate) > 0 ? targetStartDate : firstEventStartDate;
- log.info("RawUsageOptimizer rawEventStartDate = {}, firstEventStartDate = {}", result, firstEventStartDate);
return result;
}
+ private boolean containsNullEntries(final LocalDate[] entries) {
+ boolean result = false;
+ for (final LocalDate entry : entries) {
+ if (entry == null) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ public static class RawUsageOptimizerResult {
+
+ private final LocalDate firstEventStartDate;
+ private final LocalDate rawUsageStartDate;
+ private final List<RawUsage> rawUsage;
+
+ public RawUsageOptimizerResult(final LocalDate firstEventStartDate, final LocalDate rawUsageStartDate, final List<RawUsage> rawUsage) {
+ this.firstEventStartDate = firstEventStartDate;
+ this.rawUsageStartDate = rawUsageStartDate;
+ this.rawUsage = rawUsage;
+ }
+
+ public LocalDate getFirstEventStartDate() {
+ return firstEventStartDate;
+ }
+
+ public LocalDate getRawUsageStartDate() {
+ return rawUsageStartDate;
+ }
+
+ public List<RawUsage> getRawUsage() {
+ return rawUsage;
+ }
+ }
+
}
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 9dfa26a..66f7449 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
@@ -68,11 +68,13 @@ public class SubscriptionConsumableInArrear {
private final List<BillingEvent> subscriptionBillingEvents;
private final LocalDate targetDate;
private final List<RawUsage> rawSubscriptionUsage;
+ private final LocalDate rawUsageStartDate;
- public SubscriptionConsumableInArrear(final UUID invoiceId, final List<BillingEvent> subscriptionBillingEvents, final List<RawUsage> rawUsage, LocalDate targetDate) {
+ public SubscriptionConsumableInArrear(final UUID invoiceId, final List<BillingEvent> subscriptionBillingEvents, final List<RawUsage> rawUsage, final LocalDate targetDate, final LocalDate rawUsageStartDate) {
this.invoiceId = invoiceId;
this.subscriptionBillingEvents = subscriptionBillingEvents;
this.targetDate = targetDate;
+ this.rawUsageStartDate = rawUsageStartDate;
// Extract raw usage for that subscription and sort it by date
this.rawSubscriptionUsage = Ordering.<RawUsage>from(RAW_USAGE_DATE_COMPARATOR).sortedCopy(Iterables.filter(rawUsage, new Predicate<RawUsage>() {
@Override
@@ -127,7 +129,7 @@ public class SubscriptionConsumableInArrear {
// Add inflight usage interval if non existent
ContiguousIntervalConsumableInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
if (existingInterval == null) {
- existingInterval = new ContiguousIntervalConsumableInArrear(usage, invoiceId, rawSubscriptionUsage, targetDate);
+ existingInterval = new ContiguousIntervalConsumableInArrear(usage, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate);
inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
}
// Add billing event for that usage interval
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 0830548..38915b6 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
@@ -113,6 +113,10 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final DefaultUsage usage = createDefaultUsage(usageName, BillingPeriod.MONTHLY, tier1, tier2);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
+
+// createContiguousIntervalConsumableInArrear(final DefaultUsage usage, List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+
+
final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
Collections.<Usage>emptyList())
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 f7e6870..7434bd8 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
@@ -73,7 +73,7 @@ public class TestSubscriptionConsumableInArrear extends TestUsageInArrearBase {
LocalDate targetDate = new LocalDate(2013, 6, 23);
- final SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(invoiceId, billingEvents, ImmutableList.<RawUsage>of(), targetDate);
+ final SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(invoiceId, billingEvents, ImmutableList.<RawUsage>of(), targetDate, new LocalDate(dt1, DateTimeZone.UTC));
final List<ContiguousIntervalConsumableInArrear> result = foo.computeInArrearUsageInterval();
assertEquals(result.size(), 3);
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 412e96d..6a61d18 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
@@ -71,7 +71,7 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
}
protected ContiguousIntervalConsumableInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
- final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableInArrear(usage, invoiceId, rawUsages, targetDate);
+ final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableInArrear(usage, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()));
for (BillingEvent event : events) {
intervalConsumableInArrear.addBillingEvent(event);
}
@@ -84,7 +84,7 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
usage.setName(usageName);
usage.setBillingMode(BillingMode.IN_ARREAR);
usage.setUsageType(UsageType.CONSUMABLE);
- usage.setBillingPeriod(BillingPeriod.MONTHLY);
+ usage.setBillingPeriod(billingPeriod);
usage.setTiers(tiers);
return usage;
}