killbill-memoizeit

invoice: make sure we never insert notifications in the past Signed-off-by:

5/4/2018 12:25:03 PM

Details

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 504dafe..e6c094e 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
@@ -664,6 +664,26 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
                                                                                              final BillingPeriod billingPeriod,
                                                                                              final List<PlanPhasePriceOverride> overrides,
                                                                                              final NextEvent... events) {
+        return createBaseEntitlementWithPriceOverrideAndCheckForCompletion(accountId,
+                                                                           bundleExternalKey,
+                                                                           productName,
+                                                                           productCategory,
+                                                                           billingPeriod,
+                                                                           overrides,
+                                                                           null,
+                                                                           PriceListSet.DEFAULT_PRICELIST_NAME,
+                                                                           events);
+    }
+
+    protected DefaultEntitlement createBaseEntitlementWithPriceOverrideAndCheckForCompletion(final UUID accountId,
+                                                                                             final String bundleExternalKey,
+                                                                                             final String productName,
+                                                                                             final ProductCategory productCategory,
+                                                                                             final BillingPeriod billingPeriod,
+                                                                                             final List<PlanPhasePriceOverride> overrides,
+                                                                                             final LocalDate billingEffectiveDate,
+                                                                                             final String priceList,
+                                                                                             final NextEvent... events) {
         if (productCategory == ProductCategory.ADD_ON) {
             throw new RuntimeException("Unxepected Call for creating ADD_ON");
         }
@@ -672,8 +692,8 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
             @Override
             public Entitlement apply(@Nullable final Void dontcare) {
                 try {
-                    final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-                    final UUID entitlementId = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, overrides, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+                    final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, billingPeriod, priceList, null);
+                    final UUID entitlementId = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, overrides, null, billingEffectiveDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
                     assertNotNull(entitlementId);
                     return entitlementApi.getEntitlementForId(entitlementId, callContext);
                 } catch (final EntitlementApiException e) {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
index 7cbed63..ebb14a2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
@@ -70,7 +70,6 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
         addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
     }
 
-
     @Test(groups = "slow")
     public void testInvoiceNotificationWithFutureSubscriptionEvents() throws Exception {
         clock.setDay(new LocalDate(2018, 1, 31));
@@ -79,11 +78,9 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
         final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
         accountChecker.checkAccount(account.getId(), accountData, callContext);
 
-
         final LocalDate billingDate = new LocalDate(2018, 2, 28);
         final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial");
 
-
         busHandler.pushExpectedEvents(NextEvent.BLOCK);
         final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, null, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
         busHandler.assertListenerStatus();
@@ -103,11 +100,45 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
         // Move to the notification before the start date =>  2018, 3, 21
         addDaysAndCheckForCompletion(21, NextEvent.INVOICE_NOTIFICATION);
 
-
         // Move to the change date => 2018, 3, 28
         addDaysAndCheckForCompletion(7, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-
     }
 
+    @Test(groups = "slow")
+    public void testInvoiceNotificationInThePast() throws Exception {
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
 
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // Future create the entitlement
+        // Note: we need to use a plan without a trial to verify the fix, because we don't send invoice notifications for $0 invoices
+        final DefaultEntitlement bpSubscription = createBaseEntitlementWithPriceOverrideAndCheckForCompletion(account.getId(),
+                                                                                                              "bundleKey",
+                                                                                                              "Pistol",
+                                                                                                              ProductCategory.BASE,
+                                                                                                              BillingPeriod.MONTHLY,
+                                                                                                              null,
+                                                                                                              new LocalDate(2012, 4, 2),
+                                                                                                              "notrial",
+                                                                                                              NextEvent.BLOCK);
+
+        // Move to subscription start date
+        addDaysAndCheckForCompletion(1, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        // Move to notification date
+        addDaysAndCheckForCompletion(23, NextEvent.INVOICE_NOTIFICATION);
+
+        // Move to next invoice
+        addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        // Move to notification date
+        addDaysAndCheckForCompletion(24, NextEvent.INVOICE_NOTIFICATION);
+
+        // Move to next invoice
+        addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 5c26d11..10ced74 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -1006,16 +1006,20 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                              final InternalCallContext internalCallContext) {
         for (final LocalDate notificationDate : callbackDateTimePerSubscriptions.getNotificationsForTrigger().keySet()) {
             final DateTime notificationDateTime = internalCallContext.toUTCDateTime(notificationDate);
-            final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForTrigger().get(notificationDate);
-            nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, callbackDateTimePerSubscriptions.isRescheduled(), internalCallContext);
+            if (notificationDateTime.compareTo(internalCallContext.getCreatedDate()) > 0) {
+                final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForTrigger().get(notificationDate);
+                nextBillingDatePoster.insertNextBillingNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, callbackDateTimePerSubscriptions.isRescheduled(), internalCallContext);
+            }
         }
 
         final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(internalCallContext).getMillis();
         if (dryRunNotificationTime > 0) {
             for (final LocalDate notificationDate : callbackDateTimePerSubscriptions.getNotificationsForDryRun().keySet()) {
                 final DateTime notificationDateTime = internalCallContext.toUTCDateTime(notificationDate);
-                final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForDryRun().get(notificationDate);
-                nextBillingDatePoster.insertNextBillingDryRunNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, notificationDateTime.plusMillis((int) dryRunNotificationTime), internalCallContext);
+                if (notificationDateTime.compareTo(internalCallContext.getCreatedDate()) > 0) {
+                    final Set<UUID> subscriptionIds = callbackDateTimePerSubscriptions.getNotificationsForDryRun().get(notificationDate);
+                    nextBillingDatePoster.insertNextBillingDryRunNotificationFromTransaction(entitySqlDaoWrapperFactory, accountId, subscriptionIds, notificationDateTime, notificationDateTime.plusMillis((int) dryRunNotificationTime), internalCallContext);
+                }
             }
         }
     }