killbill-uncached
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java 45(+22 -23)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java 2(+1 -1)
invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java 6(+3 -3)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 18(+2 -16)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 19(+17 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java 25(+24 -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 3bc9cb9..8971143 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
@@ -95,7 +95,7 @@ public interface SubscriptionBaseInternalApi {
public Iterable<DateTime> getFutureNotificationsForAccount(InternalCallContext context);
- public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext);
+ public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final List<SubscriptionBaseTransitionType> eventTypes, final InternalCallContext internalCallContext);
public void updateBCD(final UUID subscriptionId, final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
index b9bbbb8..fc68f6a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
@@ -53,6 +53,10 @@ import static org.testng.Assert.assertEquals;
public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
+
+ private static final DryRunArguments DRY_RUN_UPCOMING_INVOICE_ARG = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE);
+ private static final DryRunArguments DRY_RUN_TARGET_DATE_ARG = new TestDryRunArguments(DryRunType.TARGET_DATE);
+
//
// Basic test with one subscription that verifies the behavior of using invoice dryRun api with no date
//
@@ -81,8 +85,7 @@ public class TestIntegrationDryRunInvoice 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(DryRunType.UPCOMING_INVOICE);
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
// Move through time and verify we get the same invoice
@@ -95,7 +98,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
// This will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 7, 14), new LocalDate(2015, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
// Move through time and verify we get the same invoice
@@ -108,7 +111,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
// One more time, this will verify that the upcoming invoice notification is found and the invoice is generated at the right date, with correct items
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 8, 14), new LocalDate(2015, 9, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
}
@@ -143,8 +146,7 @@ public class TestIntegrationDryRunInvoice 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(DryRunType.UPCOMING_INVOICE);
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -156,7 +158,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
// Since we only have one subscription next dryRun will show the annual
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);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
expectedInvoices.clear();
@@ -173,7 +175,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
// Verify next dryRun invoice and then move the clock to that date to also verify real invoice is the same
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 14), new LocalDate(2015, 2, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -183,18 +185,17 @@ public class TestIntegrationDryRunInvoice 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, however we do see both recurring items
- final DryRunArguments dryRunWIthSubscription = new TestDryRunArguments(DryRunType.UPCOMING_INVOICE, null, null, null, null, null, null, subscriptionMonthly.getId(), null, null, null);
+ // 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, and also we only see the item for the monthly subscription
+ final DryRunArguments dryRunUpcomingInvoiceWithFilterArg = 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);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRunUpcomingInvoiceWithFilterArg, 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);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -205,7 +206,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
expectedInvoices.clear();
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, dryRun, callContext);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
}
@@ -230,8 +231,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
assertListenerStatus();
// Generate a dryRun invoice on the billing startDate
- final DryRunArguments dryRunArguments1 = new TestDryRunArguments(DryRunType.TARGET_DATE);
- final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments1, callContext);
+ final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, DRY_RUN_TARGET_DATE_ARG, callContext);
assertEquals(dryRunInvoice1.getInvoiceItems().size(), 1);
assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
@@ -239,19 +239,19 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
// Generate a dryRun invoice with a plan change
- final DryRunArguments dryRunArguments = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
+ final DryRunArguments dryRunSubscriptionActionArg = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
SubscriptionEventType.CHANGE, createdEntitlement.getId(), createdEntitlement.getBundleId(), futureDate, BillingActionPolicy.IMMEDIATE);
// First one day prior subscription starts
try {
- invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunArguments, callContext);
+ invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunSubscriptionActionArg, callContext);
fail("Should fail to trigger dryRun invoice prior subscription starts");
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
}
// Second, on the startDate
- final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments, callContext);
+ final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunSubscriptionActionArg, callContext);
assertEquals(dryRunInvoice2.getInvoiceItems().size(), 1);
assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
@@ -448,9 +448,8 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
//
// 1. We verify that a DryRunType.TARGET_DATE for 2015-2-14 leads to an invoice that **only** contains the MONTHLY item (fix for #774)
//
- final DryRunArguments dryRun = new TestDryRunArguments(DryRunType.TARGET_DATE, null, null, null, null, null, null, null, null, null, null);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 14), new LocalDate(2015, 3, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 14), dryRun, callContext);
+ Invoice dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 14), DRY_RUN_TARGET_DATE_ARG, callContext);
assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 14));
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
expectedInvoices.clear();
@@ -459,7 +458,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
// 2. We verify that a DryRunType.TARGET_DATE for 2015-2-3 leads to an invoice that **only** contains the 2nd ANNUAL item (fix for #774)
//
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 2, 3), new LocalDate(2016, 2, 3), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
- dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 3), dryRun, callContext);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(2015, 2, 3), DRY_RUN_TARGET_DATE_ARG, callContext);
assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 3));
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
expectedInvoices.clear();
@@ -467,7 +466,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
// 3. We verify that UPCOMING_INVOICE leads to next invoice fo the ANNUAL
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, new TestDryRunArguments(DryRunType.UPCOMING_INVOICE), callContext);
+ dryRunInvoice = invoiceUserApi.triggerInvoiceGeneration(account.getId(), null, DRY_RUN_UPCOMING_INVOICE_ARG, callContext);
assertEquals(dryRunInvoice.getTargetDate(), new LocalDate(2015, 2, 1));
invoiceChecker.checkInvoiceNoAudits(dryRunInvoice, callContext, expectedInvoices);
expectedInvoices.clear();
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 93bc808..49d2288 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -293,7 +293,7 @@ public class InvoiceDispatcher {
return new DefaultInvoice(input);
}
}));
- Invoice invoice = null;
+ Invoice invoice;
if (!isDryRun) {
invoice = processAccountWithLockAndInputTargetDate(accountId, inputTargetDate, billingEvents, existingInvoices, false, context);
if (parkedAccount) {
@@ -304,15 +304,19 @@ public class InvoiceDispatcher {
log.warn("Unable to unpark account", ignored);
}
}
- } else {
+ } else /* Dry run use cases */ {
+ if (dryRunArguments.getDryRunType() == DryRunType.UPCOMING_INVOICE) {
- final Iterable<UUID> filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsForDryRun(dryRunArguments, billingEvents);
- final List<LocalDate> candidateTargetDates = getUpcomingInvoiceCandidateDates(filteredSubscriptionIdsForDryRun, context);
+ final Iterable<UUID> filteredSubscriptionIdsForDryRun = getFilteredSubscriptionIdsFor_UPCOMING_INVOICE_DryRun(dryRunArguments, billingEvents);
+ final List<LocalDate> candidateTargetDates = getUpcomingInvoiceCandidateDates(filteredSubscriptionIdsForDryRun, context);
- if (dryRunArguments.getDryRunType() == DryRunType.UPCOMING_INVOICE) {
- invoice = processDryRun_UPCOMING_INVOICE_Invoice(accountId, candidateTargetDates, billingEvents, existingInvoices, context);
+ if (Iterables.isEmpty(filteredSubscriptionIdsForDryRun)) {
+ invoice = processDryRun_UPCOMING_INVOICE_Invoice(accountId, candidateTargetDates, billingEvents, existingInvoices, context);
+ } else {
+ invoice = processDryRun_UPCOMING_INVOICE_FILTERING_Invoice(accountId, candidateTargetDates, billingEvents, existingInvoices, context);
+ }
} else /* DryRunType.TARGET_DATE */ {
- invoice = processDryRun_TARGET_DATE_Invoice(accountId, inputTargetDate, candidateTargetDates, billingEvents, existingInvoices, context);
+ invoice = processDryRun_TARGET_DATE_Invoice(accountId, inputTargetDate, billingEvents, existingInvoices, context);
}
}
return invoice;
@@ -345,21 +349,22 @@ public class InvoiceDispatcher {
}
- private Invoice processDryRun_TARGET_DATE_Invoice(final UUID accountId, final LocalDate targetDate, final List<LocalDate> upcomingTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
-
- // Look for first invoice notification date prior our targetDate
- LocalDate prevLocalDate = null;
- for (final LocalDate cur : upcomingTargetDates) {
- if (cur.compareTo(targetDate) < 0) {
- prevLocalDate = cur;
+ private Invoice processDryRun_UPCOMING_INVOICE_FILTERING_Invoice(final UUID accountId, final List<LocalDate> candidateTargetDates, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
+ for (final LocalDate curTargetDate : candidateTargetDates) {
+ final Invoice invoice = processDryRun_TARGET_DATE_Invoice(accountId, curTargetDate, billingEvents, existingInvoices, context);
+ if (invoice != null) {
+ return invoice;
}
}
+ return null;
+ }
+
+
+ private Invoice processDryRun_TARGET_DATE_Invoice(final UUID accountId, final LocalDate targetDate, final BillingEventSet billingEvents, final List<Invoice> existingInvoices, final InternalCallContext context) throws InvoiceApiException {
+
// Generate a dryRun invoice for such date if required in such a way that dryRun invoice on our targetDate only contains items that we expect to see
- Invoice additionalInvoice = null;
- if (prevLocalDate != null) {
- additionalInvoice = processAccountWithLockAndInputTargetDate(accountId, prevLocalDate, billingEvents, existingInvoices, true, context);
- }
+ final Invoice additionalInvoice = processAccountWithLockAndInputTargetDate(accountId, targetDate.minusDays(1), billingEvents, existingInvoices, true, context);
final List<Invoice> augmentedExistingInvoices = additionalInvoice != null ?
new ImmutableList.Builder().addAll(existingInvoices).add(additionalInvoice).build() :
@@ -394,7 +399,7 @@ public class InvoiceDispatcher {
}
}
- private Iterable<UUID> getFilteredSubscriptionIdsForDryRun(@Nullable final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) {
+ private Iterable<UUID> getFilteredSubscriptionIdsFor_UPCOMING_INVOICE_DryRun(@Nullable final DryRunArguments dryRunArguments, final BillingEventSet billingEvents) {
if (dryRunArguments == null ||
!dryRunArguments.getDryRunType().equals(DryRunType.UPCOMING_INVOICE) ||
(dryRunArguments.getSubscriptionId() == null && dryRunArguments.getBundleId() == null)) {
@@ -544,7 +549,7 @@ public class InvoiceDispatcher {
// If dryRunNotification is enabled we also need to fetch the upcoming PHASE dates (we add SubscriptionNotification with isForInvoiceNotificationTrigger = false)
final boolean isInvoiceNotificationEnabled = invoiceConfig.getDryRunNotificationSchedule(context).getMillis() > 0;
if (isInvoiceNotificationEnabled) {
- final Map<UUID, DateTime> upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(SubscriptionBaseTransitionType.PHASE, context);
+ final Map<UUID, DateTime> upcomingPhasesForSubscriptions = subscriptionApi.getNextFutureEventForSubscriptions(ImmutableList.<SubscriptionBaseTransitionType>of(SubscriptionBaseTransitionType.PHASE), context);
for (final UUID cur : upcomingPhasesForSubscriptions.keySet()) {
final DateTime curDate = upcomingPhasesForSubscriptions.get(cur);
List<SubscriptionNotification> resultValue = result.get(cur);
@@ -717,10 +722,35 @@ public class InvoiceDispatcher {
}
}
+
+
private List<LocalDate> getUpcomingInvoiceCandidateDates(final Iterable<UUID> filteredSubscriptionIds, final InternalCallContext internalCallContext) {
final Iterable<DateTime> nextScheduledInvoiceDates = getNextScheduledInvoiceEffectiveDate(filteredSubscriptionIds, internalCallContext);
- final Iterable<DateTime> nextScheduledSubscriptionsEventDates = subscriptionApi.getFutureNotificationsForAccount(internalCallContext);
- return Lists.<DateTime, LocalDate>transform(UPCOMING_NOTIFICATION_DATE_ORDERING.sortedCopy(Iterables.<DateTime>concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEventDates)),
+
+ // Events that are prone to trigger an invoice
+ // TODO we can't specify START_BILLING_DISABLED and END_BILLING_DISABLED as those are generated by junction
+ final ImmutableList<SubscriptionBaseTransitionType> eventTypes = ImmutableList.<SubscriptionBaseTransitionType>of(SubscriptionBaseTransitionType.CREATE,
+ SubscriptionBaseTransitionType.CHANGE,
+ SubscriptionBaseTransitionType.CANCEL,
+ SubscriptionBaseTransitionType.PHASE,
+ SubscriptionBaseTransitionType.BCD_CHANGE);
+
+ final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap = subscriptionApi.getNextFutureEventForSubscriptions(eventTypes, internalCallContext);
+
+ final Iterable<DateTime> nextScheduledSubscriptionsEvents;
+ if (!Iterables.isEmpty(filteredSubscriptionIds)) {
+ List<DateTime> tmp = new ArrayList<DateTime>();
+ for (final UUID curSubscriptionId : nextScheduledSubscriptionsEventMap.keySet()) {
+ if (Iterables.contains(filteredSubscriptionIds, curSubscriptionId)) {
+ tmp.add(nextScheduledSubscriptionsEventMap.get(curSubscriptionId));
+ }
+ }
+ nextScheduledSubscriptionsEvents = tmp;
+ } else {
+ nextScheduledSubscriptionsEvents = nextScheduledSubscriptionsEventMap.values();
+ }
+
+ return Lists.<DateTime, LocalDate>transform(UPCOMING_NOTIFICATION_DATE_ORDERING.sortedCopy(Iterables.<DateTime>concat(nextScheduledInvoiceDates, nextScheduledSubscriptionsEvents)),
new Function<DateTime, LocalDate>() {
@Override
public LocalDate apply(final DateTime input) {
@@ -739,9 +769,9 @@ public class InvoiceDispatcher {
for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
// If we don't specify a filter list of subscriptionIds, we look at all events.
- boolean isEventForSubscription = !filteredSubscriptionIds.iterator().hasNext();
+ boolean isEventForSubscription = Iterables.isEmpty(filteredSubscriptionIds);
// If we specify a filter, we keep the date if at least one of the subscriptions from the event list matches one of the subscription from our filter list
- if (filteredSubscriptionIds.iterator().hasNext()) {
+ if (!Iterables.isEmpty(filteredSubscriptionIds)) {
for (final UUID curSubscriptionId : filteredSubscriptionIds) {
if (Iterables.contains(input.getEvent().getUuidKeys(), curSubscriptionId)) {
isEventForSubscription = true;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
index 1717ddc..c11d8dc 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -103,7 +103,7 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
nextBillingQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), futureNotificationTime,
newNotificationEvent, internalCallContext.getUserToken(),
internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
- } else if (log.isDebugEnabled()) {
+ } else {
log.info("Updating next billing date notification event at {} for subscriptionId {}", futureNotificationTime.toString(), subscriptionId.toString());
final NextBillingDateNotificationKey updateNotificationEvent = new NextBillingDateNotificationKey(existingNotificationForEffectiveDate.getEvent(), ImmutableList.of(subscriptionId));
nextBillingQueue.updateFutureNotification(existingNotificationForEffectiveDate.getRecordId(), updateNotificationEvent, internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
index a44d298..1b64c60 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/NextBillingDateNotificationKey.java
@@ -24,6 +24,7 @@ import org.killbill.notificationq.DefaultUUIDNotificationKey;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
@@ -44,14 +45,13 @@ public class NextBillingDateNotificationKey extends DefaultUUIDNotificationKey {
}
public NextBillingDateNotificationKey(NextBillingDateNotificationKey existing,
- final Iterable<UUID> newUUIDKeys){
+ final Iterable<UUID> newUUIDKeys) {
super(null);
- this.uuidKeys = ImmutableList.copyOf(Iterables.concat(existing.getUuidKeys(), newUUIDKeys));
+ this.uuidKeys = ImmutableSet.copyOf(Iterables.concat(existing.getUuidKeys(), newUUIDKeys));
this.targetDate = existing.getTargetDate();
this.isDryRunForInvoiceNotification = existing.isDryRunForInvoiceNotification();
}
-
@JsonProperty("isDryRunForInvoiceNotification")
public Boolean isDryRunForInvoiceNotification() {
return isDryRunForInvoiceNotification;
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 0fd3a0b..8cf40a2 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
@@ -776,22 +776,8 @@ 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) {
- switch (input.getType()) {
- case PHASE:
- return eventType == SubscriptionBaseTransitionType.PHASE;
- case BCD_UPDATE:
- return eventType == SubscriptionBaseTransitionType.BCD_CHANGE;
- case API_USER:
- default:
- return true;
- }
- }
- });
+ public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final List<SubscriptionBaseTransitionType> eventTypes, final InternalCallContext internalCallContext) {
+ final Iterable<SubscriptionBaseEvent> filteredEvents = dao.getFutureEventsForAccount(eventTypes, internalCallContext);
final Map<UUID, DateTime> result = filteredEvents.iterator().hasNext() ? new HashMap<UUID, DateTime>() : ImmutableMap.<UUID, DateTime>of();
for (final SubscriptionBaseEvent cur : filteredEvents) {
final DateTime targetDate = result.get(cur.getSubscriptionId());
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 d629e61..b57afd4 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
@@ -530,13 +530,28 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
@Override
- public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final List<SubscriptionBaseTransitionType> eventTypes, final InternalTenantContext context) {
+
+
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Iterable<SubscriptionBaseEvent>>() {
@Override
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 Iterable<SubscriptionEventModelDao> filteredActiveEvents = Iterables.filter(activeEvents, new Predicate<SubscriptionEventModelDao>() {
+ @Override
+ public boolean apply(final SubscriptionEventModelDao input) {
+ for (final SubscriptionBaseTransitionType e : eventTypes) {
+ if (input.isOfSubscriptionBaseTransitionType(e)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+
+ return Iterables.transform(filteredActiveEvents, new Function<SubscriptionEventModelDao, SubscriptionBaseEvent>() {
@Override
public SubscriptionBaseEvent apply(final SubscriptionEventModelDao input) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
index 9063bba..c5d14bc 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
@@ -19,6 +19,7 @@ package org.killbill.billing.subscription.engine.dao.model;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.events.EventBaseBuilder;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
@@ -35,7 +36,6 @@ import org.killbill.billing.util.entity.dao.EntityModelDao;
import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
public class SubscriptionEventModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBaseEvent> {
-
private long totalOrdering;
private EventType eventType;
private ApiEventType userType;
@@ -216,6 +216,29 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
return result;
}
+ public boolean isOfSubscriptionBaseTransitionType(final SubscriptionBaseTransitionType type) {
+ switch(type) {
+ case CREATE:
+ return eventType == EventType.API_USER && userType == ApiEventType.CREATE;
+ case TRANSFER:
+ return eventType == EventType.API_USER && userType == ApiEventType.TRANSFER;
+ case CHANGE:
+ return eventType == EventType.API_USER && userType == ApiEventType.CHANGE;
+ case CANCEL:
+ return eventType == EventType.API_USER && userType == ApiEventType.CANCEL;
+ case UNCANCEL:
+ return eventType == EventType.API_USER && userType == ApiEventType.UNCANCEL;
+ case PHASE:
+ return eventType == EventType.PHASE;
+ case BCD_CHANGE:
+ return eventType == EventType.BCD_UPDATE;
+ case START_BILLING_DISABLED:
+ case END_BILLING_DISABLED:
+ default:
+ return false;
+ }
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
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 886e43a..c48270a 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
@@ -25,6 +25,7 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.transfer.BundleTransferData;
import org.killbill.billing.subscription.api.transfer.TransferCancelData;
@@ -75,7 +76,7 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public SubscriptionBaseEvent getEventById(UUID eventId, InternalTenantContext context);
- public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(InternalTenantContext context);
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(List<SubscriptionBaseTransitionType> eventTypes, InternalTenantContext context);
public List<SubscriptionBaseEvent> getEventsForSubscription(UUID subscriptionId, InternalTenantContext context);
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 271bcad..4555e03 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
@@ -38,6 +38,7 @@ import org.killbill.billing.catalog.api.TimeUnit;
import org.killbill.billing.dao.MockNonEntityDao;
import org.killbill.billing.entitlement.api.SubscriptionApiException;
import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
import org.killbill.billing.subscription.api.transfer.BundleTransferData;
import org.killbill.billing.subscription.api.transfer.TransferCancelData;
@@ -471,6 +472,11 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
return null;
}
+ @Override
+ public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final List<SubscriptionBaseTransitionType> eventTypes, final InternalTenantContext context) {
+ return null;
+ }
+
private void recordFutureNotificationFromTransaction(final EntitySqlDaoWrapperFactory transactionalDao, final DateTime effectiveDate,
final NotificationEvent notificationKey, final InternalCallContext context) {
try {
@@ -499,10 +505,6 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
}
}
- @Override
- public Iterable<SubscriptionBaseEvent> getFutureEventsForAccount(final InternalTenantContext context) {
- return null;
- }
@Override
public void transfer(final UUID srcAccountId, final UUID destAccountId, final BundleTransferData data,