killbill-aplcache
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java 5(+4 -1)
invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java 3(+2 -1)
pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 23(+23 -0)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 34(+12 -22)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 3(+2 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java 7(+6 -1)
Details
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index 1b8af6b..b42b21f 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -23,7 +23,6 @@ import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
-
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
@@ -83,4 +82,5 @@ public interface SubscriptionBaseInternalApi {
public Iterable<DateTime> getFutureNotificationsForAccount(InternalCallContext context);
+ public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext);
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
index 613ce72..25abfe5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
@@ -52,8 +52,11 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+ // Move to end of trial => 2012, 4, 24
+ addDaysAndCheckForCompletion(23, NextEvent.INVOICE_NOTIFICATION);
+
// Move to end of trial => 2012, 5, 1
- addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ addDaysAndCheckForCompletion(7, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
// Next invoice is scheduled for 2012, 6, 1 so we should have a NOTIFICATION event 7 days before, on 2012, 5, 25
addDaysAndCheckForCompletion(24, NextEvent.INVOICE_NOTIFICATION);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
index 78696a0..47b0034 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
@@ -25,6 +25,7 @@ import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,7 +85,7 @@ public class DefaultInvoiceMigrationApi implements InvoiceMigrationApi {
final DateTime wrongEffectiveDateButDoesNotMatter = null;
final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(wrongEffectiveDateButDoesNotMatter, account.getTimeZone(), clock);
dao.createInvoice(migrationInvoice, ImmutableList.<InvoiceItemModelDao>of(migrationInvoiceItem),
- true, new FutureAccountNotifications(dateAndTimeZoneContext, ImmutableMap.<UUID, List<DateTime>>of()), internalCallContextFactory.createInternalCallContext(accountId, context));
+ true, new FutureAccountNotifications(dateAndTimeZoneContext, ImmutableMap.<UUID, List<SubscriptionNotification>>of()), internalCallContextFactory.createInternalCallContext(accountId, context));
return migrationInvoice.getId();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index a02f2f4..742f3f3 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -36,6 +36,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItemType;
@@ -793,14 +794,16 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule().getMillis();
final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
for (final UUID subscriptionId : callbackDateTimePerSubscriptions.getNotifications().keySet()) {
- final List<DateTime> callbackDateTimeUTC = callbackDateTimePerSubscriptions.getNotifications().get(subscriptionId);
- for (final DateTime cur : callbackDateTimeUTC) {
+ final List<SubscriptionNotification> callbackDateTimeUTC = callbackDateTimePerSubscriptions.getNotifications().get(subscriptionId);
+ for (final SubscriptionNotification cur : callbackDateTimeUTC) {
if (isInvoiceNotificationEnabled) {
- final DateTime curDryRunNotificationTime = cur.minus(dryRunNotificationTime);
+ final DateTime curDryRunNotificationTime = cur.getEffectiveDate().minus(dryRunNotificationTime);
final DateTime effectiveCurDryRunNotificationTime = (curDryRunNotificationTime.isAfter(clock.getUTCNow())) ? curDryRunNotificationTime : clock.getUTCNow();
- nextBillingDatePoster.insertNextBillingDryRunNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, effectiveCurDryRunNotificationTime, cur, callbackDateTimePerSubscriptions.getAccountDateAndTimeZoneContext(), internalCallContext);
+ nextBillingDatePoster.insertNextBillingDryRunNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, effectiveCurDryRunNotificationTime, cur.getEffectiveDate(), callbackDateTimePerSubscriptions.getAccountDateAndTimeZoneContext(), internalCallContext);
+ }
+ if (cur.isForInvoiceNotificationTrigger()) {
+ nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, cur.getEffectiveDate(), callbackDateTimePerSubscriptions.getAccountDateAndTimeZoneContext(), internalCallContext);
}
- nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionId, cur, callbackDateTimePerSubscriptions.getAccountDateAndTimeZoneContext(), internalCallContext);
}
}
}
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 88bd542..d229093 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -51,6 +51,7 @@ import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
import org.killbill.billing.events.InvoiceInternalEvent;
import org.killbill.billing.events.InvoiceNotificationInternalEvent;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.DefaultInvoiceService;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.Invoice;
@@ -77,10 +78,12 @@ 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;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.config.InvoiceConfig;
import org.killbill.billing.util.globallocker.LockerType;
import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
import org.killbill.bus.api.PersistentBus;
@@ -128,6 +131,7 @@ public class InvoiceDispatcher {
private final PersistentBus eventBus;
private final Clock clock;
private final NotificationQueueService notificationQueueService;
+ private final InvoiceConfig invoiceConfig;
@Inject
public InvoiceDispatcher(final InvoiceGenerator generator,
@@ -141,6 +145,7 @@ public class InvoiceDispatcher {
final GlobalLocker locker,
final PersistentBus eventBus,
final NotificationQueueService notificationQueueService,
+ final InvoiceConfig invoiceConfig,
final Clock clock) {
this.generator = generator;
this.billingApi = billingApi;
@@ -154,6 +159,7 @@ public class InvoiceDispatcher {
this.eventBus = eventBus;
this.clock = clock;
this.notificationQueueService = notificationQueueService;
+ this.invoiceConfig = invoiceConfig;
}
public void processSubscriptionForInvoiceGeneration(final EffectiveSubscriptionInternalEvent transition,
@@ -221,8 +227,7 @@ public class InvoiceDispatcher {
final boolean isDryRun = dryRunArguments != null;
// inputTargetDateTime is only allowed in dryRun mode to have the system compute it
- Preconditions.checkArgument(inputTargetDateTime != null || isDryRun);
-
+ Preconditions.checkArgument(inputTargetDateTime != null || isDryRun, "inputTargetDateTime is required in non dryRun mode");
try {
// Make sure to first set the BCD if needed then get the account object (to have the BCD set)
final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, dryRunArguments, context);
@@ -288,7 +293,7 @@ public class InvoiceDispatcher {
final CallContext callContext = buildCallContext(context);
invoice.addInvoiceItems(invoicePluginDispatcher.getAdditionalInvoiceItems(invoice, callContext));
if (!isDryRun) {
- commitInvoiceStateAndNotifyAccountIfConfugured(account, invoice, billingEvents, dateAndTimeZoneContext, targetDate, context);
+ commitInvoiceStateAndNotifyAccountIfConfigured(account, invoice, billingEvents, dateAndTimeZoneContext, targetDate, context);
}
return invoice;
} catch (final AccountApiException e) {
@@ -300,7 +305,7 @@ public class InvoiceDispatcher {
}
}
- private void commitInvoiceStateAndNotifyAccountIfConfugured(final Account account, final Invoice invoice, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final LocalDate targetDate, final InternalCallContext context) throws SubscriptionBaseApiException, InvoiceApiException {
+ private void commitInvoiceStateAndNotifyAccountIfConfigured(final Account account, final Invoice invoice, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final LocalDate targetDate, final InternalCallContext context) throws SubscriptionBaseApiException, InvoiceApiException {
boolean isRealInvoiceWithNonEmptyItems = false;
// Extract the set of invoiceId for which we see items that don't belong to current generated invoice
final Set<UUID> adjustedUniqueOtherInvoiceId = new TreeSet<UUID>();
@@ -330,7 +335,7 @@ public class InvoiceDispatcher {
return new InvoiceItemModelDao(input);
}
});
- final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceItemModelDaos, billingEvents, dateAndTimeZoneContext);
+ final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceItemModelDaos, billingEvents, dateAndTimeZoneContext, context);
// We filter any zero amount for USAGE items prior we generate the invoice, which may leave us with an invoice with no items;
// we recompute the isRealInvoiceWithItems flag based on what is left (the call to invoice is still necessary to set the future notifications).
@@ -401,9 +406,9 @@ public class InvoiceDispatcher {
}
@VisibleForTesting
- FutureAccountNotifications createNextFutureNotificationDate(final Iterable<InvoiceItemModelDao> invoiceItems, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext) {
+ FutureAccountNotifications createNextFutureNotificationDate(final Iterable<InvoiceItemModelDao> invoiceItems, final BillingEventSet billingEvents, final DateAndTimeZoneContext dateAndTimeZoneContext, final InternalCallContext context) {
- final Map<UUID, List<DateTime>> result = new HashMap<UUID, List<DateTime>>();
+ final Map<UUID, List<SubscriptionNotification>> result = new HashMap<UUID, List<SubscriptionNotification>>();
final Map<String, LocalDate> perSubscriptionUsage = new HashMap<String, LocalDate>();
@@ -412,9 +417,9 @@ public class InvoiceDispatcher {
//
for (final InvoiceItemModelDao item : invoiceItems) {
- List<DateTime> perSubscriptionCallback = result.get(item.getSubscriptionId());
+ List<SubscriptionNotification> perSubscriptionCallback = result.get(item.getSubscriptionId());
if (perSubscriptionCallback == null && (item.getType() == InvoiceItemType.RECURRING || item.getType() == InvoiceItemType.USAGE)) {
- perSubscriptionCallback = new ArrayList<DateTime>();
+ perSubscriptionCallback = new ArrayList<SubscriptionNotification>();
result.put(item.getSubscriptionId(), perSubscriptionCallback);
}
@@ -423,7 +428,7 @@ public class InvoiceDispatcher {
if ((item.getEndDate() != null) &&
(item.getAmount() == null ||
item.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
- perSubscriptionCallback.add(dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(item.getEndDate()));
+ perSubscriptionCallback.add(new SubscriptionNotification(dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(item.getEndDate()), true));
}
break;
@@ -444,14 +449,28 @@ public class InvoiceDispatcher {
final String[] parts = key.split(":");
final UUID subscriptionId = UUID.fromString(parts[0]);
- final List<DateTime> perSubscriptionCallback = result.get(subscriptionId);
+ final List<SubscriptionNotification> perSubscriptionCallback = result.get(subscriptionId);
final String usageName = parts[1];
final LocalDate endDate = perSubscriptionUsage.get(key);
final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(subscriptionId, usageName, endDate, dateAndTimeZoneContext, billingEvents);
- perSubscriptionCallback.add(subscriptionUsageCallbackDate);
+ perSubscriptionCallback.add(new SubscriptionNotification(subscriptionUsageCallbackDate, true));
}
+ // If dryRunNotification is enabled we also need to fetch the upcoming PHASE dates (we add SubscriptionNotification with isForInvoiceNotificationTrigger = false)
+ final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule().getMillis() > 0;
+ if (isInvoiceNotificationEnabled) {
+ final Map<UUID, DateTime> upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(SubscriptionBaseTransitionType.PHASE, context);
+ for (UUID cur : upcomingPhasesForSubscriptions.keySet()) {
+ final DateTime curDate = upcomingPhasesForSubscriptions.get(cur);
+ List<SubscriptionNotification> resultValue = result.get(cur);
+ if (resultValue == null) {
+ resultValue = new ArrayList<SubscriptionNotification>();
+ }
+ resultValue.add(new SubscriptionNotification(curDate, false));
+ result.put(cur, resultValue);
+ }
+ }
return new FutureAccountNotifications(dateAndTimeZoneContext, result);
}
@@ -515,9 +534,9 @@ public class InvoiceDispatcher {
public static class FutureAccountNotifications {
private final DateAndTimeZoneContext accountDateAndTimeZoneContext;
- private final Map<UUID, List<DateTime>> notifications;
+ private final Map<UUID, List<SubscriptionNotification>> notifications;
- public FutureAccountNotifications(final DateAndTimeZoneContext accountDateAndTimeZoneContext, final Map<UUID, List<DateTime>> notifications) {
+ public FutureAccountNotifications(final DateAndTimeZoneContext accountDateAndTimeZoneContext, final Map<UUID, List<SubscriptionNotification>> notifications) {
this.accountDateAndTimeZoneContext = accountDateAndTimeZoneContext;
this.notifications = notifications;
}
@@ -526,9 +545,29 @@ public class InvoiceDispatcher {
return accountDateAndTimeZoneContext;
}
- public Map<UUID, List<DateTime>> getNotifications() {
+ public Map<UUID, List<SubscriptionNotification>> getNotifications() {
return notifications;
}
+
+ public static class SubscriptionNotification {
+
+ private final DateTime effectiveDate;
+ private final boolean isForNotificationTrigger;
+
+
+ public SubscriptionNotification(final DateTime effectiveDate, final boolean isForNotificationTrigger) {
+ this.effectiveDate = effectiveDate;
+ this.isForNotificationTrigger = isForNotificationTrigger;
+ }
+
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ public boolean isForInvoiceNotificationTrigger() {
+ return isForNotificationTrigger;
+ }
+ }
}
private List<DateTime> getUpcomingInvoiceCandidateDates(final InternalCallContext internalCallContext) {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
index a01f7b8..346fa22 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
@@ -28,6 +28,7 @@ import org.killbill.billing.invoice.api.InvoiceMigrationApi;
import org.killbill.billing.invoice.api.InvoicePaymentApi;
import org.killbill.billing.invoice.api.InvoiceService;
import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.invoice.calculator.InvoiceCalculatorUtils;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.generator.InvoiceGenerator;
import org.killbill.billing.invoice.glue.TestInvoiceModuleWithEmbeddedDb;
@@ -41,6 +42,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.util.api.TagUserApi;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.InvoiceConfig;
import org.killbill.billing.util.dao.NonEntityDao;
import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
@@ -110,6 +112,8 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
protected TestInvoiceNotificationQListener testInvoiceNotificationQListener;
@Inject
protected InvoicePluginDispatcher invoicePluginDispatcher;
+ @Inject
+ protected InvoiceConfig invoiceConfig;
@Override
protected KillbillConfigSource getConfigSource() {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index 6bcc93b..5929b0b 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -21,7 +21,6 @@ package org.killbill.billing.invoice;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -40,6 +39,7 @@ import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.TestInvoiceHelper.DryRunFutureDateArguments;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.Invoice;
@@ -96,7 +96,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, clock);
+ null, invoiceConfig, clock);
Invoice invoice = dispatcher.processAccount(accountId, target, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice);
@@ -149,7 +149,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, clock);
+ null, invoiceConfig, clock);
final Invoice invoice = dispatcher.processAccount(account.getId(), new DateTime("2012-07-30T00:00:00.000Z"), null, context);
Assert.assertNotNull(invoice);
@@ -207,18 +207,18 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, clock);
+ null, invoiceConfig, clock);
- final FutureAccountNotifications futureAccountNotifications = dispatcher.createNextFutureNotificationDate(Collections.singletonList(item), null, dateAndTimeZoneContext);
+ final FutureAccountNotifications futureAccountNotifications = dispatcher.createNextFutureNotificationDate(Collections.singletonList(item), null, dateAndTimeZoneContext, context);
Assert.assertEquals(futureAccountNotifications.getNotifications().size(), 1);
- final List<DateTime> receivedDates = futureAccountNotifications.getNotifications().get(item.getSubscriptionId());
+ final List<SubscriptionNotification> receivedDates = futureAccountNotifications.getNotifications().get(item.getSubscriptionId());
Assert.assertEquals(receivedDates.size(), 1);
- final LocalDate receivedTargetDate = new LocalDate(receivedDates.get(0), DateTimeZone.forID("Pacific/Pitcairn"));
+ final LocalDate receivedTargetDate = new LocalDate(receivedDates.get(0).getEffectiveDate(), DateTimeZone.forID("Pacific/Pitcairn"));
Assert.assertEquals(receivedTargetDate, endDate);
- Assert.assertTrue(receivedDates.get(0).compareTo(new DateTime(2012, 11, 27, 1, 12, 23, DateTimeZone.UTC)) <= 0);
+ Assert.assertTrue(receivedDates.get(0).getEffectiveDate().compareTo(new DateTime(2012, 11, 27, 1, 12, 23, DateTimeZone.UTC)) <= 0);
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 808abb6..9d8dc34 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -50,12 +50,14 @@ import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceNotifier;
import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.calculator.InvoiceCalculatorUtils;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceItemSqlDao;
@@ -76,6 +78,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.InvoiceConfig;
import org.killbill.billing.util.currency.KillBillMoney;
import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
import org.killbill.clock.Clock;
@@ -153,15 +156,16 @@ public class TestInvoiceHelper {
private final Clock clock;
private final InternalCallContext internalCallContext;
private final InternalCallContextFactory internalCallContextFactory;
-
+ private final InvoiceConfig invoiceConfig;
// Low level SqlDao used by the tests to directly insert rows
private final InvoicePaymentSqlDao invoicePaymentSqlDao;
private final InvoiceItemSqlDao invoiceItemSqlDao;
+
@Inject
public TestInvoiceHelper(final InvoiceGenerator generator, final IDBI dbi,
final BillingInternalApi billingApi, final AccountInternalApi accountApi, final InvoicePluginDispatcher invoicePluginDispatcher, final AccountUserApi accountUserApi, final SubscriptionBaseInternalApi subscriptionApi, final BusService busService,
- final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final InternalCallContext internalCallContext,
+ final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final InternalCallContext internalCallContext, final InvoiceConfig invoiceConfig,
final InternalCallContextFactory internalCallContextFactory) {
this.generator = generator;
this.billingApi = billingApi;
@@ -177,6 +181,7 @@ public class TestInvoiceHelper {
this.internalCallContextFactory = internalCallContextFactory;
this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
+ this.invoiceConfig = invoiceConfig;
}
public UUID generateRegularInvoice(final Account account, final DateTime targetDate, final CallContext callContext) throws Exception {
@@ -198,7 +203,7 @@ public class TestInvoiceHelper {
final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi,
invoiceDao, internalCallContextFactory, invoiceNotifier, invoicePluginDispatcher, locker, busService.getBus(),
- null, clock);
+ null, invoiceConfig, clock);
Invoice invoice = dispatcher.processAccount(account.getId(), targetDate, new DryRunFutureDateArguments(), internalCallContext);
Assert.assertNotNull(invoice);
@@ -281,7 +286,7 @@ public class TestInvoiceHelper {
// The test does not use the invoice callback notifier hence the empty map
final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.UTC, clock);
- invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, isRealInvoiceWithItems, new FutureAccountNotifications(dateAndTimeZoneContext, ImmutableMap.<UUID, List<DateTime>>of()), internalCallContext);
+ invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, isRealInvoiceWithItems, new FutureAccountNotifications(dateAndTimeZoneContext, ImmutableMap.<UUID, List<SubscriptionNotification>>of()), internalCallContext);
}
public void createPayment(final InvoicePayment invoicePayment, final InternalCallContext internalCallContext) {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
index 871915a..67bf392 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
@@ -31,6 +31,7 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.SubscriptionNotification;
import org.killbill.billing.invoice.TestInvoiceHelper;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
@@ -108,7 +109,7 @@ public class InvoiceTestUtils {
final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.UTC, clock);
- invoiceDao.createInvoice(new InvoiceModelDao(invoice), invoiceModelItems, true, new FutureAccountNotifications(dateAndTimeZoneContext, ImmutableMap.<UUID, List<DateTime>>of()), internalCallContext);
+ invoiceDao.createInvoice(new InvoiceModelDao(invoice), invoiceModelItems, true, new FutureAccountNotifications(dateAndTimeZoneContext, ImmutableMap.<UUID, List<SubscriptionNotification>>of()), internalCallContext);
return invoice;
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 97e4fb7..d45a84b 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -321,8 +321,18 @@ public class InvoiceResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request,
@javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, InvoiceApiException {
final CallContext callContext = context.createContext(createdBy, reason, comment, request);
- // We allow special value UPCOMING_INVOICE_TARGET_DATE where the system will automatically generate the resulting targetDate for upcoming invoice
- final LocalDate inputDate = targetDate != null && targetDate.equals(UPCOMING_INVOICE_TARGET_DATE) ? null : toLocalDate(UUID.fromString(accountId), targetDate, callContext);
+ final LocalDate inputDate;
+ // In the case of subscription dryRun we set the targetDate to be the effective date of the change itself
+ if (dryRunSubscriptionSpec != null && dryRunSubscriptionSpec.getEffectiveDate() != null) {
+ inputDate = dryRunSubscriptionSpec.getEffectiveDate();
+ // In case of Invoice dryRun we also allow the special value UPCOMING_INVOICE_TARGET_DATE where the system will automatically
+ // generate the resulting targetDate for upcoming invoice; in terms of invoice api that maps to passing a null targetDate
+ } else if (targetDate != null && targetDate.equals(UPCOMING_INVOICE_TARGET_DATE)) {
+ inputDate = null;
+ // Finally, in case of Invoice dryRun, we allow a null input date (will default to NOW), or extract the value provided
+ } else {
+ inputDate = toLocalDate(UUID.fromString(accountId), targetDate, callContext);
+ }
// Passing a null or empty body means we are trying to generate an invoice with a (future) targetDate
// On the other hand if body is not null, we are attempting a dryRun subscription operation
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index d8048b6..26072aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.11</version>
+ <version>0.12</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.14.1-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
index b0c427d..c48147d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
@@ -94,8 +94,7 @@ public class TestInvoice extends TestJaxrsBase {
assertEquals(firstInvoiceByNumberJson, invoiceJson);
// Then create a dryRun for next upcoming invoice
- LocalDate futureDate = null;
- final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), futureDate, null, createdBy, reason, comment);
+ final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), null, true, null, createdBy, reason, comment);
assertEquals(dryRunInvoice.getBalance(), new BigDecimal("249.95"));
assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2012, 6, 25));
assertEquals(dryRunInvoice.getItems().size(), 1);
@@ -103,7 +102,7 @@ public class TestInvoice extends TestJaxrsBase {
assertEquals(dryRunInvoice.getItems().get(0).getEndDate(), new LocalDate(2012, 7, 25));
assertEquals(dryRunInvoice.getItems().get(0).getAmount(), new BigDecimal("249.95"));
- futureDate = dryRunInvoice.getTargetDate();
+ final LocalDate futureDate = dryRunInvoice.getTargetDate();
// The one more time with no DryRun
killBillClient.createInvoice(accountJson.getAccountId(), futureDate, createdBy, reason, comment);
@@ -122,7 +121,7 @@ public class TestInvoice extends TestJaxrsBase {
final Account accountJson = createAccountWithDefaultPaymentMethod();
final InvoiceDryRun dryRunArg = new InvoiceDryRun(SubscriptionEventType.START_BILLING,
null, "Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, null, null, null, null, null, null);
- final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), new LocalDate(initialDate, DateTimeZone.forID(accountJson.getTimeZone())), dryRunArg, createdBy, reason, comment);
+ final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), new LocalDate(initialDate, DateTimeZone.forID(accountJson.getTimeZone())), false, dryRunArg, createdBy, reason, comment);
assertEquals(dryRunInvoice.getItems().size(), 1);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 561e8cb..c273969 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -52,6 +52,7 @@ import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.subscription.api.SubscriptionApiBase;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.DefaultEffectiveSubscriptionEvent;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseApiService;
@@ -67,6 +68,7 @@ import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseServ
import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -84,8 +86,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.inject.Inject;
@@ -510,6 +514,25 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
}
+ @Override
+ public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext) {
+ final Iterable<SubscriptionBaseEvent> events = dao.getFutureEventsForAccount(internalCallContext);
+ final Iterable<SubscriptionBaseEvent> filteredEvents = Iterables.filter(events, new Predicate<SubscriptionBaseEvent>() {
+ @Override
+ public boolean apply(final SubscriptionBaseEvent input) {
+ return (eventType == SubscriptionBaseTransitionType.PHASE && input.getType() == EventType.PHASE) || input.getType() != EventType.PHASE;
+ }
+ });
+ final Map<UUID, DateTime> result = filteredEvents.iterator().hasNext() ? new HashMap<UUID, DateTime>() : ImmutableMap.<UUID, DateTime>of();
+ for (SubscriptionBaseEvent cur : filteredEvents) {
+ final DateTime targetDate = result.get(cur.getSubscriptionId());
+ if (targetDate == null || targetDate.compareTo(cur.getEffectiveDate()) > 0) {
+ result.put(cur.getSubscriptionId(), cur.getEffectiveDate());
+ }
+ }
+ return result;
+ }
+
private DateTime getBundleStartDateWithSanity(final UUID bundleId, @Nullable final DefaultSubscriptionBase baseSubscription, final Plan plan,
final DateTime requestedDate, final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
switch (plan.getProduct().getCategory()) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 21b377d..075f841 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -102,6 +102,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleModelDao, SubscriptionBaseBundle, SubscriptionApiException> implements SubscriptionDao {
@@ -447,30 +448,19 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public Map<UUID, List<SubscriptionBaseEvent>> getEventsForBundle(final UUID bundleId, final InternalTenantContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Map<UUID, List<SubscriptionBaseEvent>>>() {
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
+ return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Iterable<SubscriptionBaseEvent>>() {
@Override
- public Map<UUID, List<SubscriptionBaseEvent>> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final SubscriptionSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
-
- final List<SubscriptionModelDao> subscriptionModels = transactional.getSubscriptionsFromBundleId(bundleId.toString(), context);
- if (subscriptionModels.size() == 0) {
- return Collections.emptyMap();
- }
+ public Iterable<SubscriptionBaseEvent> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
+ final List<SubscriptionEventModelDao> activeEvents = transactional.getFutureActiveEventsForAccount(clock.getUTCNow().toDate(), context);
+ return Iterables.transform(activeEvents, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
- final SubscriptionEventSqlDao eventsDaoFromSameTransaction = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
- final Map<UUID, List<SubscriptionBaseEvent>> result = new HashMap<UUID, List<SubscriptionBaseEvent>>();
- for (final SubscriptionModelDao cur : subscriptionModels) {
- final List<SubscriptionEventModelDao> eventModels = eventsDaoFromSameTransaction.getEventsForSubscription(cur.getId().toString(), context);
- final List<SubscriptionBaseEvent> events = new ArrayList<SubscriptionBaseEvent>(Collections2.transform(eventModels, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
- @Override
- public SubscriptionBaseEvent apply(@Nullable final SubscriptionEventModelDao input) {
- return SubscriptionEventModelDao.toSubscriptionEvent(input);
- }
- }));
- result.put(cur.getId(), events);
- }
- return result;
+ @Override
+ public SubscriptionBaseEvent apply(final SubscriptionEventModelDao input) {
+ return SubscriptionEventModelDao.toSubscriptionEvent(input);
+ }
+ });
}
});
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java
index 91f3a2e..6225134 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/RepairSubscriptionDao.java
@@ -297,7 +297,7 @@ public class RepairSubscriptionDao extends EntityDaoBase<SubscriptionBundleModel
}
@Override
- public Map<UUID, List<SubscriptionBaseEvent>> getEventsForBundle(final UUID bundleId, final InternalTenantContext context) {
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
throw new SubscriptionBaseError(NOT_IMPLEMENTED);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 78aa6af..5503b99 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -74,7 +74,7 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public SubscriptionBaseEvent getEventById(UUID eventId, InternalTenantContext context);
- public Map<UUID, List<SubscriptionBaseEvent>> getEventsForBundle(UUID bundleId, InternalTenantContext context);
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(InternalTenantContext context);
public List<SubscriptionBaseEvent> getEventsForSubscription(UUID subscriptionId, InternalTenantContext context);
@@ -101,5 +101,6 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
// Repair
public void repair(UUID accountId, UUID bundleId, List<SubscriptionDataRepair> inRepair, InternalCallContext context);
+
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index dd4656a..177df8b 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -59,5 +59,10 @@ public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventM
@SqlQuery
public List<SubscriptionEventModelDao> getEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
- @BindBean final InternalTenantContext context);
+ @BindBean final InternalTenantContext context);
+
+
+ @SqlQuery
+ public List<SubscriptionEventModelDao> getFutureActiveEventsForAccount(@Bind("now") Date now, @BindBean final InternalTenantContext context);
+
}
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index 4550610..c7e915a 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -111,3 +111,17 @@ and is_active = 1
;
>>
+getFutureActiveEventsForAccount() ::= <<
+select <allTableFields()>
+, record_id as total_ordering
+from <tableName()>
+where
+account_record_id = :accountRecordId
+and is_active = 1
+and effective_date > :now
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
+;
+>>
+
+
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index 12eea30..1891725 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -481,7 +481,7 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
@Override
- public Map<UUID, List<SubscriptionBaseEvent>> getEventsForBundle(final UUID bundleId, final InternalTenantContext context) {
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
return null;
}