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();