killbill-aplcache

invoice: Modify dryRun api to allow queriying for upcoming

10/4/2015 9:01:33 PM

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index 442f5f4..98c0ce5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -40,6 +40,7 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItemType;
@@ -75,7 +76,7 @@ public class TestIntegration extends TestIntegrationBase {
         // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
         //
 
-        TestDryRunArguments dryRun = new TestDryRunArguments("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null,
+        TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null,
                                                              SubscriptionEventType.START_BILLING, null, null, clock.getUTCNow(), null);
         Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
@@ -90,7 +91,7 @@ public class TestIntegration extends TestIntegrationBase {
         //
         // ADD ADD_ON ON THE SAME DAY
         //
-        dryRun = new TestDryRunArguments("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, null, null,
+        dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, null, null,
                                          SubscriptionEventType.START_BILLING, null, bpSubscription.getBundleId(), clock.getUTCNow(), null);
         dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95")));
@@ -106,7 +107,7 @@ public class TestIntegration extends TestIntegrationBase {
         // CANCEL BP ON THE SAME DAY (we should have two cancellations, BP and AO)
         // There is no invoice created as we only adjust the previous invoice.
         //
-        dryRun = new TestDryRunArguments(null, null, null, null, null, SubscriptionEventType.STOP_BILLING, bpSubscription.getId(),
+        dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, null, null, null, null, null, SubscriptionEventType.STOP_BILLING, bpSubscription.getId(),
                                          bpSubscription.getBundleId(), clock.getUTCNow(), null);
         dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-399.95")));
@@ -153,7 +154,7 @@ public class TestIntegration extends TestIntegrationBase {
         //
         // CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
         //
-        TestDryRunArguments dryRun = new TestDryRunArguments("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE,
+        TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE,
                                                              subscription.getId(), subscription.getBundleId(), clock.getUTCNow(), null);
         Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
         expectedInvoices.add(new ExpectedInvoiceItemCheck(initialCreationDate.toLocalDate(), null, InvoiceItemType.FIXED, new BigDecimal("0")));
@@ -173,7 +174,7 @@ public class TestIntegration extends TestIntegrationBase {
         setDateAndCheckForCompletion(new DateTime(2012, 3, 1, 23, 59, 59, 0, testTimeZone));
 
         DateTime nextDate = clock.getUTCNow().plusDays(1);
-        dryRun = new TestDryRunArguments();
+        dryRun = new TestDryRunArguments(DryRunType.TARGET_DATE);
 
 
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2), new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("561.24")));
@@ -202,7 +203,7 @@ public class TestIntegration extends TestIntegrationBase {
 
 
         nextDate = clock.getUTCNow().plusDays(31);
-        dryRun = new TestDryRunArguments();
+        dryRun = new TestDryRunArguments(DryRunType.TARGET_DATE);
         dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(nextDate, testTimeZone), dryRun, callContext);
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 31), new LocalDate(2012, 4, 30), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
 
@@ -287,7 +288,7 @@ public class TestIntegration extends TestIntegrationBase {
         //
 
 
-        TestDryRunArguments dryRun = new TestDryRunArguments("Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE,
+        TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, null, null, SubscriptionEventType.CHANGE,
                                                              subscription.getId(), subscription.getBundleId(), null, null);
         try {
            invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index c962709..3a17e84 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -61,6 +61,7 @@ import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.entitlement.api.SubscriptionApi;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
 import org.killbill.billing.invoice.api.DryRunArguments;
+import org.killbill.billing.invoice.api.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoicePaymentApi;
@@ -710,6 +711,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
 
     protected static class TestDryRunArguments implements DryRunArguments {
 
+        private final DryRunType dryRunType;
         private final PlanPhaseSpecifier spec;
         private final SubscriptionEventType action;
         private final UUID subscriptionId;
@@ -717,7 +719,8 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         private final DateTime effectiveDate;
         private final BillingActionPolicy billingPolicy;
 
-        public TestDryRunArguments() {
+        public TestDryRunArguments(final DryRunType dryRunType) {
+            this.dryRunType = dryRunType;
             this.spec = null;
             this.action = null;
             this.subscriptionId = null;
@@ -726,7 +729,8 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
             this.billingPolicy = null;
         }
 
-        public TestDryRunArguments(final String productName,
+        public TestDryRunArguments(final DryRunType dryRunType,
+                                   final String productName,
                                    final ProductCategory category,
                                    final BillingPeriod billingPeriod,
                                    final String priceList,
@@ -736,6 +740,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                                    final UUID bundleId,
                                    final DateTime effectiveDate,
                                    final BillingActionPolicy billingPolicy) {
+            this.dryRunType = dryRunType;
             this.spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceList, phaseType);
             this.action = action;
             this.subscriptionId = subscriptionId;
@@ -745,6 +750,11 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }
 
         @Override
+        public DryRunType getDryRunType() {
+            return dryRunType;
+        }
+
+        @Override
         public PlanPhaseSpecifier getPlanPhaseSpecifier() {
             return spec;
         }
@@ -775,7 +785,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }
 
         @Override
-        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+        public List<PlanPhasePriceOverride> getPlanPhasePriceOverrides() {
             return null;
         }
     }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index 362c1c5..9e8941c 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -33,6 +33,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.invoice.api.DryRunArguments;
+import org.killbill.billing.invoice.api.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.payment.api.Payment;
@@ -78,7 +79,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
 
         // This will verify that the upcoming Phase is found and the invoice is generated at the right date, with correct items
-        DryRunArguments dryRun = new TestDryRunArguments();
+        DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
         Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
         invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
 
@@ -144,12 +145,12 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2014, 2, 1), new LocalDate(2015, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
 
-        DryRunArguments dryRun = new TestDryRunArguments();
+        DryRunArguments dryRun = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
         Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
         invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
 
         busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
-        // 2014-1-2
+        // 2014-2-1
         clock.addDays(30);
         assertListenerStatus();
         invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
@@ -184,7 +185,17 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext, expectedInvoices);
         expectedInvoices.clear();
 
-        //
+
+        // We test first the next expected invoice for a specific subscription: We can see the targetDate is 2015-2-14 and not 2015-2-1
+        final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null);
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunWIthSubscription, callContext);
+        assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
+        invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Then we test first the next expected invoice at the account level
         expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 1), new LocalDate(2016, 2, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
         dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
         invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index 3dc6b0e..0d21887 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunType;
 import org.testng.annotations.Test;
 
 import org.killbill.billing.account.api.Account;
@@ -87,7 +88,7 @@ public class TestSubscription extends TestIntegrationBase {
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2334.20")),
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("2164.88"), false /* Issue with test where created date for context is wrong*/));
 
-        TestDryRunArguments dryRun = new TestDryRunArguments(productName, ProductCategory.BASE, BillingPeriod.MONTHLY, null, null,
+        TestDryRunArguments dryRun = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, productName, ProductCategory.BASE, BillingPeriod.MONTHLY, null, null,
                                                              SubscriptionEventType.CHANGE, bpEntitlement.getId(), bpEntitlement.getBundleId(), null, BillingActionPolicy.IMMEDIATE);
         Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), dryRun, callContext);
         invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, toBeChecked);
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 aa5198e..7764c4a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -53,6 +53,7 @@ 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.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
@@ -75,6 +76,7 @@ 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;
@@ -115,7 +117,7 @@ public class InvoiceDispatcher {
 
     private static final Ordering<DateTime> UPCOMING_NOTIFICATION_DATE_ORDERING = Ordering.natural();
     private final static Joiner JOINER_COMMA = Joiner.on(",");
-    private static final NullDryRunArguments NULL_DRY_RUN_ARGUMENTS = new NullDryRunArguments();
+    private static final TargetDateDryRunArguments TARGET_DATE_DRY_RUN_ARGUMENTS = new TargetDateDryRunArguments();
 
     private final InvoiceGenerator generator;
     private final BillingInternalApi billingApi;
@@ -191,7 +193,7 @@ public class InvoiceDispatcher {
                 return null;
             }
             final UUID accountId = subscriptionApi.getAccountIdFromSubscriptionId(subscriptionId, context);
-            final DryRunArguments dryRunArguments = dryRunForNotification ? NULL_DRY_RUN_ARGUMENTS : null;
+            final DryRunArguments dryRunArguments = dryRunForNotification ? TARGET_DATE_DRY_RUN_ARGUMENTS : null;
 
             return processAccount(accountId, targetDate, dryRunArguments, context);
         } catch (final SubscriptionBaseApiException e) {
@@ -225,14 +227,17 @@ public class InvoiceDispatcher {
 
         final boolean isDryRun = dryRunArguments != null;
         // A null inputTargetDateTime is only allowed in dryRun mode to have the system compute it
-        Preconditions.checkArgument(inputTargetDateTime != null || isDryRun, "inputTargetDateTime is required in non dryRun mode");
+        Preconditions.checkArgument(inputTargetDateTime != null ||
+                                    (dryRunArguments != null && DryRunType.UPCOMING_INVOICE.equals(dryRunArguments.getDryRunType())), "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);
             if (billingEvents.isEmpty()) {
                 return null;
             }
-            final List<DateTime> candidateDateTimes = (inputTargetDateTime != null) ? ImmutableList.of(inputTargetDateTime) : getUpcomingInvoiceCandidateDates(context);
+            final List<DateTime> candidateDateTimes = (inputTargetDateTime != null) ?
+                                                      ImmutableList.of(inputTargetDateTime) :
+                                                      getUpcomingInvoiceCandidateDates(getFilteredSubscriptionIds(dryRunArguments, billingEvents), context);
             for (final DateTime curTargetDateTime : candidateDateTimes) {
                 final Invoice invoice = processAccountWithLockAndInputTargetDate(accountId, curTargetDateTime, billingEvents, isDryRun, context);
                 if (invoice != null) {
@@ -246,6 +251,30 @@ public class InvoiceDispatcher {
         }
     }
 
+
+    private Iterable<UUID> getFilteredSubscriptionIds(final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) {
+        if (!dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) ||
+            (dryRunArguments.getSubscriptionId() == null && dryRunArguments.getBundleId() == null)) {
+            return ImmutableList.<UUID>of();
+        }
+
+        if (dryRunArguments.getSubscriptionId() != null) {
+            return ImmutableList.of(dryRunArguments.getSubscriptionId());
+        }
+
+        return Iterables.transform(Iterables.filter(billingEvents, new Predicate<BillingEvent>() {
+            @Override
+            public boolean apply(final BillingEvent input) {
+                return input.getSubscription().getBundleId().equals(dryRunArguments.getBundleId());
+            }
+        }), new Function<BillingEvent, UUID>() {
+            @Override
+            public UUID apply(final BillingEvent input) {
+                return input.getSubscription().getId();
+            }
+        });
+    }
+
     private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, final DateTime targetDateTime,
                                                              final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException {
         try {
@@ -572,30 +601,31 @@ public class InvoiceDispatcher {
         }
     }
 
-    private List<DateTime> getUpcomingInvoiceCandidateDates(final InternalCallContext internalCallContext) {
-        final Iterable<DateTime> nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(internalCallContext);
+    private List<DateTime> getUpcomingInvoiceCandidateDates(final Iterable<UUID> filteredSubscriptionIds, final InternalCallContext internalCallContext) {
+        final Iterable<DateTime> nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(filteredSubscriptionIds, internalCallContext);
         final Iterable<DateTime> nextScheduledSubscriptionsEventDates = subscriptionApi.getFutureNotificationsForAccount(internalCallContext);
         Iterables.concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEventDates);
         return UPCOMING_NOTIFICATION_DATE_ORDERING.sortedCopy(Iterables.concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEventDates));
     }
 
-    private Iterable<DateTime> getNextScheduledInvoiceEffectiveDate(final InternalCallContext internalCallContext) {
+    private Iterable<DateTime> getNextScheduledInvoiceEffectiveDate(final Iterable<UUID> filteredSubscriptionIds, final InternalCallContext internalCallContext) {
         try {
             final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
                                                                                                       DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
             final List<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
 
-            final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> filtered = Iterables.filter(futureNotifications, new Predicate<NotificationEventWithMetadata<NextBillingDateNotificationKey>>() {
+            final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> allUpcomingEvents = Iterables.filter(futureNotifications, new Predicate<NotificationEventWithMetadata<NextBillingDateNotificationKey>>() {
                 @Override
                 public boolean apply(@Nullable final NotificationEventWithMetadata<NextBillingDateNotificationKey> input) {
+                    final boolean isEventForSubscription = !filteredSubscriptionIds.iterator().hasNext() || Iterables.contains(filteredSubscriptionIds, input.getEvent().getUuidKey());
 
                     final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
                                                                   input.getEvent().isDryRunForInvoiceNotification() : false;
-                    return !isEventDryRunForNotifications;
+                    return isEventForSubscription && !isEventDryRunForNotifications;
                 }
             });
 
-            return Iterables.transform(filtered, new Function<NotificationEventWithMetadata<NextBillingDateNotificationKey>, DateTime>() {
+            return Iterables.transform(allUpcomingEvents, new Function<NotificationEventWithMetadata<NextBillingDateNotificationKey>, DateTime>() {
                 @Nullable
                 @Override
                 public DateTime apply(@Nullable final NotificationEventWithMetadata<NextBillingDateNotificationKey> input) {
@@ -607,7 +637,12 @@ public class InvoiceDispatcher {
         }
     }
 
-    private final static class NullDryRunArguments implements DryRunArguments {
+    private final static class TargetDateDryRunArguments implements DryRunArguments {
+
+        @Override
+        public DryRunType getDryRunType() {
+            return DryRunType.TARGET_DATE;
+        }
 
         @Override
         public PlanPhaseSpecifier getPlanPhaseSpecifier() {
@@ -638,9 +673,8 @@ public class InvoiceDispatcher {
         public BillingActionPolicy getBillingActionPolicy() {
             return null;
         }
-
         @Override
-        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+        public List<PlanPhasePriceOverride> getPlanPhasePriceOverrides() {
             return null;
         }
     }
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 20a9007..0ff2d28 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -52,6 +52,7 @@ 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.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
@@ -422,6 +423,11 @@ public class TestInvoiceHelper {
         }
 
         @Override
+        public DryRunType getDryRunType() {
+            return DryRunType.TARGET_DATE;
+        }
+
+        @Override
         public PlanPhaseSpecifier getPlanPhaseSpecifier() {
             return null;
         }
@@ -452,7 +458,7 @@ public class TestInvoiceHelper {
         }
 
         @Override
-        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+        public List<PlanPhasePriceOverride> getPlanPhasePriceOverrides() {
             return null;
         }
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java
index 9bc33ac..bc82ab3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceDryRunJson.java
@@ -28,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class InvoiceDryRunJson {
 
+    private final String dryRunType;
     private final String dryRunAction;
     private final String phaseType;
     private final String productName;
@@ -41,7 +42,8 @@ public class InvoiceDryRunJson {
     private final List<PhasePriceOverrideJson> priceOverrides;
 
     @JsonCreator
-    public InvoiceDryRunJson(@JsonProperty("dryRunAction") @Nullable final String dryRunAction,
+    public InvoiceDryRunJson(@JsonProperty("dryRunType") @Nullable final String dryRunType,
+                             @JsonProperty("dryRunAction") @Nullable final String dryRunAction,
                              @JsonProperty("phaseType") @Nullable final String phaseType,
                              @JsonProperty("productName") @Nullable final String productName,
                              @JsonProperty("productCategory") @Nullable final String productCategory,
@@ -52,6 +54,7 @@ public class InvoiceDryRunJson {
                              @JsonProperty("effectiveDate") @Nullable final LocalDate effectiveDate,
                              @JsonProperty("billingPolicy") @Nullable final String billingPolicy,
                              @JsonProperty("priceOverrides") @Nullable final List<PhasePriceOverrideJson> priceOverrides) {
+        this.dryRunType = dryRunType;
         this.dryRunAction = dryRunAction;
         this.phaseType = phaseType;
         this.productName = productName;
@@ -65,6 +68,10 @@ public class InvoiceDryRunJson {
         this.priceOverrides = priceOverrides;
     }
 
+    public String getDryRunType() {
+        return dryRunType;
+    }
+
     public String getDryRunAction() {
         return dryRunAction;
     }
@@ -129,6 +136,9 @@ public class InvoiceDryRunJson {
         if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
             return false;
         }
+        if (dryRunType != null ? !dryRunType.equals(that.dryRunType) : that.dryRunType != null) {
+            return false;
+        }
         if (dryRunAction != null ? !dryRunAction.equals(that.dryRunAction) : that.dryRunAction != null) {
             return false;
         }
@@ -160,6 +170,7 @@ public class InvoiceDryRunJson {
     @Override
     public int hashCode() {
         int result = dryRunAction != null ? dryRunAction.hashCode() : 0;
+        result = 31 * result + (dryRunType != null ? dryRunType.hashCode() : 0);
         result = 31 * result + (phaseType != null ? phaseType.hashCode() : 0);
         result = 31 * result + (productName != null ? productName.hashCode() : 0);
         result = 31 * result + (productCategory != null ? productCategory.hashCode() : 0);
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 180a800..7b82b75 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
@@ -70,6 +70,7 @@ import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
 import org.killbill.billing.invoice.api.DryRunArguments;
+import org.killbill.billing.invoice.api.DryRunType;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
@@ -146,7 +147,6 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     });
 
-
     @Inject
     public InvoiceResource(final AccountUserApi accountUserApi,
                            final InvoiceUserApi invoiceApi,
@@ -315,7 +315,6 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
-
     @Timed
     @POST
     @Path("/" + DRY_RUN)
@@ -333,14 +332,14 @@ public class InvoiceResource extends JaxRsResourceBase {
                                           @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, InvoiceApiException {
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         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
+        if (dryRunSubscriptionSpec != null) {
+            if (DryRunType.UPCOMING_INVOICE.name().equals(dryRunSubscriptionSpec.getDryRunType())) {
+                inputDate = null;
+            } else if (DryRunType.SUBSCRIPTION_ACTION.name().equals(dryRunSubscriptionSpec.getDryRunType()) && dryRunSubscriptionSpec.getEffectiveDate() != null) {
+                inputDate = dryRunSubscriptionSpec.getEffectiveDate();
+            } else {
+                inputDate = toLocalDate(UUID.fromString(accountId), targetDate, callContext);
+            }
         } else {
             inputDate = toLocalDate(UUID.fromString(accountId), targetDate, callContext);
         }
@@ -540,7 +539,6 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
-
     @Timed
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
@@ -944,6 +942,7 @@ public class InvoiceResource extends JaxRsResourceBase {
 
     private static class DefaultDryRunArguments implements DryRunArguments {
 
+        private final DryRunType dryRunType;
         private final SubscriptionEventType action;
         private final UUID subscriptionId;
         private final DateTime effectiveDate;
@@ -954,6 +953,7 @@ public class InvoiceResource extends JaxRsResourceBase {
 
         public DefaultDryRunArguments(final InvoiceDryRunJson input, final DateTimeZone accountTimeZone, final Currency currency, final Clock clock) {
             if (input == null) {
+                this.dryRunType = DryRunType.TARGET_DATE;
                 this.action = null;
                 this.subscriptionId = null;
                 this.effectiveDate = null;
@@ -962,37 +962,43 @@ public class InvoiceResource extends JaxRsResourceBase {
                 this.billingPolicy = null;
                 this.overrides = null;
             } else {
+                this.dryRunType = input.getDryRunType() != null ? DryRunType.valueOf(input.getDryRunType()) : DryRunType.TARGET_DATE;
                 this.action = input.getDryRunAction() != null ? SubscriptionEventType.valueOf(input.getDryRunAction()) : null;
                 this.subscriptionId = input.getSubscriptionId() != null ? UUID.fromString(input.getSubscriptionId()) : null;
                 this.bundleId = input.getBundleId() != null ? UUID.fromString(input.getBundleId()) : null;
                 this.effectiveDate = input.getEffectiveDate() != null ? ClockUtil.computeDateTimeWithUTCReferenceTime(input.getEffectiveDate(), clock.getUTCNow().toLocalTime(), accountTimeZone, clock) : null;
                 this.billingPolicy = input.getBillingPolicy() != null ? BillingActionPolicy.valueOf(input.getBillingPolicy()) : null;
-                final PlanPhaseSpecifier planPhaseSpecifier  = (input.getProductName() != null &&
-                                     input.getProductCategory() != null &&
-                                     input.getBillingPeriod() != null) ?
-                                    new PlanPhaseSpecifier(input.getProductName(),
-                                                           ProductCategory.valueOf(input.getProductCategory()),
-                                                           BillingPeriod.valueOf(input.getBillingPeriod()),
-                                                           input.getPriceListName(),
-                                                           input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null) :
-                                    null;
+                final PlanPhaseSpecifier planPhaseSpecifier = (input.getProductName() != null &&
+                                                               input.getProductCategory() != null &&
+                                                               input.getBillingPeriod() != null) ?
+                                                              new PlanPhaseSpecifier(input.getProductName(),
+                                                                                     ProductCategory.valueOf(input.getProductCategory()),
+                                                                                     BillingPeriod.valueOf(input.getBillingPeriod()),
+                                                                                     input.getPriceListName(),
+                                                                                     input.getPhaseType() != null ? PhaseType.valueOf(input.getPhaseType()) : null) :
+                                                              null;
                 this.specifier = planPhaseSpecifier;
                 this.overrides = input.getPriceOverrides() != null ?
                                  ImmutableList.copyOf(Iterables.transform(input.getPriceOverrides(), new Function<PhasePriceOverrideJson, PlanPhasePriceOverride>() {
-                    @Nullable
-                    @Override
-                    public PlanPhasePriceOverride apply(@Nullable final PhasePriceOverrideJson input) {
-                        if (input.getPhaseName() != null) {
-                            return new DefaultPlanPhasePriceOverride(input.getPhaseName(), currency, input.getFixedPrice(), input.getRecurringPrice());
-                        } else {
-                            return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice());
-                        }
-                    }
-                })) : ImmutableList.<PlanPhasePriceOverride>of();
+                                     @Nullable
+                                     @Override
+                                     public PlanPhasePriceOverride apply(@Nullable final PhasePriceOverrideJson input) {
+                                         if (input.getPhaseName() != null) {
+                                             return new DefaultPlanPhasePriceOverride(input.getPhaseName(), currency, input.getFixedPrice(), input.getRecurringPrice());
+                                         } else {
+                                             return new DefaultPlanPhasePriceOverride(planPhaseSpecifier, currency, input.getFixedPrice(), input.getRecurringPrice());
+                                         }
+                                     }
+                                 })) : ImmutableList.<PlanPhasePriceOverride>of();
             }
         }
 
         @Override
+        public DryRunType getDryRunType() {
+            return dryRunType;
+        }
+
+        @Override
         public PlanPhaseSpecifier getPlanPhaseSpecifier() {
             return specifier;
         }
@@ -1023,7 +1029,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
 
         @Override
-        public List<PlanPhasePriceOverride> getPlanPhasePriceoverrides() {
+        public List<PlanPhasePriceOverride> getPlanPhasePriceOverrides() {
             return overrides;
         }
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 7a97f65..0735138 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -227,8 +227,6 @@ public interface JaxrsResource {
     public static final String INVOICE_TRANSLATION = "translation";
     public static final String INVOICE_CATALOG_TRANSLATION = "catalogTranslation";
 
-    public static final String UPCOMING_INVOICE_TARGET_DATE = "upcomingInvoiceTargetDate";
-
     public static final String COMBO = "combo";
 
 }
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 0c65c04..4457006 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
@@ -24,8 +24,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
-
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
@@ -40,9 +38,9 @@ import org.killbill.billing.client.model.InvoiceItem;
 import org.killbill.billing.client.model.InvoicePayment;
 import org.killbill.billing.client.model.InvoicePayments;
 import org.killbill.billing.client.model.Invoices;
-import org.killbill.billing.client.model.Payment;
 import org.killbill.billing.client.model.PaymentMethod;
 import org.killbill.billing.entitlement.api.SubscriptionEventType;
+import org.killbill.billing.invoice.api.DryRunType;
 import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
 import org.killbill.billing.util.api.AuditLevel;
 import org.testng.Assert;
@@ -52,7 +50,6 @@ import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
@@ -95,7 +92,10 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(firstInvoiceByNumberJson, invoiceJson);
 
         // Then create a dryRun for next upcoming invoice
-        final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), null, true, null, createdBy, reason, comment);
+        final InvoiceDryRun dryRunArg = new InvoiceDryRun(DryRunType.UPCOMING_INVOICE, null,
+                                                          null, null, null, null, null, null, null, null, null, null);
+
+        final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), null, dryRunArg, createdBy, reason, comment);
         assertEquals(dryRunInvoice.getBalance(), new BigDecimal("249.95"));
         assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2012, 6, 25));
         assertEquals(dryRunInvoice.getItems().size(), 1);
@@ -112,7 +112,6 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(newInvoiceList.size(), 3);
     }
 
-
     @Test(groups = "slow", description = "Can create a subscription in dryRun mode and get an invoice back")
     public void testDryRunSubscriptionCreate() throws Exception {
         final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
@@ -120,9 +119,9 @@ public class TestInvoice extends TestJaxrsBase {
 
         // "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE,
         final Account accountJson = createAccountWithDefaultPaymentMethod();
-        final InvoiceDryRun dryRunArg = new InvoiceDryRun(SubscriptionEventType.START_BILLING,
+        final InvoiceDryRun dryRunArg = new InvoiceDryRun(DryRunType.TARGET_DATE, 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())), false, dryRunArg, createdBy, reason, comment);
+        final Invoice dryRunInvoice = killBillClient.createDryRunInvoice(accountJson.getAccountId(), new LocalDate(initialDate, DateTimeZone.forID(accountJson.getTimeZone())), dryRunArg, createdBy, reason, comment);
         assertEquals(dryRunInvoice.getItems().size(), 1);
 
     }
@@ -352,7 +351,6 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
     }
 
-
     @Test(groups = "slow", description = "Can create multiple external charges")
     public void testExternalCharges() throws Exception {
         final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();