Details
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index fe88a63..3ed5c04 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -38,6 +38,7 @@ import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.InvoicePayment.InvoicePaymentType;
+import com.ning.billing.invoice.generator.InvoiceDateUtils;
import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoice;
@@ -49,6 +50,7 @@ import com.ning.billing.util.ChangeType;
import com.ning.billing.util.api.TagApiException;
import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.dao.EntityAudit;
import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.dao.TableName;
@@ -60,16 +62,19 @@ public class DefaultInvoiceDao implements InvoiceDao {
private final TagUserApi tagUserApi;
private final NextBillingDatePoster nextBillingDatePoster;
private final InvoiceItemSqlDao invoiceItemSqlDao;
+ private final Clock clock;
@Inject
public DefaultInvoiceDao(final IDBI dbi,
final NextBillingDatePoster nextBillingDatePoster,
- final TagUserApi tagUserApi) {
+ final TagUserApi tagUserApi,
+ final Clock clock) {
this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
this.nextBillingDatePoster = nextBillingDatePoster;
this.tagUserApi = tagUserApi;
+ this.clock = clock;
}
@Override
@@ -507,35 +512,29 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final UUID accountId, final int billCycleDay, final List<InvoiceItem> invoiceItems) {
- DateTime nextBCD = null;
- UUID subscriptionForNextBCD = null;
+ UUID subscriptionForNextNotification = null;
+ boolean shouldBeNotified = false;
for (final InvoiceItem item : invoiceItems) {
if (item.getInvoiceItemType() == InvoiceItemType.RECURRING) {
final RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
if ((recurringInvoiceItem.getEndDate() != null) &&
- (recurringInvoiceItem.getAmount() == null ||
- recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
- if (nextBCD == null || nextBCD.compareTo(recurringInvoiceItem.getEndDate()) > 0) {
- nextBCD = recurringInvoiceItem.getEndDate();
- subscriptionForNextBCD = recurringInvoiceItem.getSubscriptionId();
- }
+ (recurringInvoiceItem.getAmount() == null ||
+ recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ subscriptionForNextNotification = recurringInvoiceItem.getSubscriptionId();
+ shouldBeNotified = true;
+ break;
}
}
}
- // We need to be notified if and only if the maximum end date of the invoiced recurring items is equal
- // to the next bill cycle day.
- // We take the maximum because we're guaranteed to have invoiced all subscriptions up until that date
- // (and no further processing is needed).
- // Also, we only need to get notified on the BDC. For other invoice events (e.g. phase changes),
+
+ // We only need to get notified on the BCD. For other invoice events (e.g. phase changes),
// we'll be notified by entitlement.
- if (subscriptionForNextBCD != null && nextBCD != null) {
- final int lastDayOfMonth = nextBCD.dayOfMonth().withMaximumValue().getDayOfMonth();
- final int nextBCDDay = nextBCD.getDayOfMonth();
- // Small trick here in case the bill cycle day doesn't exist for that month, e.g. the bill cycle day
- // is on the 31st, but the month has only 30 days
- if (nextBCDDay == billCycleDay || (lastDayOfMonth == nextBCDDay && billCycleDay > lastDayOfMonth)) {
- nextBillingDatePoster.insertNextBillingNotification(dao, accountId, subscriptionForNextBCD, nextBCD);
- }
+ if (shouldBeNotified) {
+ // We could be notified at any time during the day at the billCycleDay - use the current time to
+ // spread the load
+ final DateTime nextNotificationDateTime = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(clock.getUTCNow(), billCycleDay);
+ // NextBillingDatePoster will ignore duplicates
+ nextBillingDatePoster.insertNextBillingNotification(dao, accountId, subscriptionForNextNotification, nextNotificationDateTime);
}
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
index 5f7f86a..df130ee 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.generator;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.MutableDateTime;
public class InvoiceDateUtils {
public static DateTime roundDateTimeToDate(final DateTime input, final DateTimeZone timeZone) {
@@ -28,4 +29,21 @@ public class InvoiceDateUtils {
return new DateTime(tzAdjustedStartDate.getYear(), tzAdjustedStartDate.getMonthOfYear(), tzAdjustedStartDate.getDayOfMonth(), 0, 0, timeZone);
}
+
+ public static DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
+ final int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+ final MutableDateTime tmp = date.toMutableDateTime();
+ if (billingCycleDay > lastDayOfMonth) {
+ tmp.setDayOfMonth(lastDayOfMonth);
+ } else {
+ tmp.setDayOfMonth(billingCycleDay);
+ }
+ DateTime proposedDate = tmp.toDateTime();
+
+ while (proposedDate.isBefore(date)) {
+ proposedDate = proposedDate.plusMonths(1);
+ }
+ return proposedDate;
+ }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index 117df30..bf146cc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -26,6 +26,7 @@ import org.joda.time.Months;
import org.joda.time.MutableDateTime;
import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.invoice.generator.InvoiceDateUtils;
public class InAdvanceBillingMode implements BillingMode {
private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
@@ -49,7 +50,7 @@ public class InAdvanceBillingMode implements BillingMode {
final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
// beginning from the start date, find the first billing date
- final DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+ final DateTime firstBillingCycleDate = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
// add pro-ration item if needed
if (firstBillingCycleDate.isAfter(startDate)) {
@@ -92,7 +93,7 @@ public class InAdvanceBillingMode implements BillingMode {
}
// beginning from the start date, find the first billing date
- final DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+ final DateTime firstBillingCycleDate = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
// add pro-ration item if needed
if (firstBillingCycleDate.isAfter(startDate)) {
@@ -124,23 +125,6 @@ public class InAdvanceBillingMode implements BillingMode {
return results;
}
- private DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
- final int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
-
- final MutableDateTime tmp = date.toMutableDateTime();
- if (billingCycleDay > lastDayOfMonth) {
- tmp.setDayOfMonth(lastDayOfMonth);
- } else {
- tmp.setDayOfMonth(billingCycleDay);
- }
- DateTime proposedDate = tmp.toDateTime();
-
- while (proposedDate.isBefore(date)) {
- proposedDate = proposedDate.plusMonths(1);
- }
- return proposedDate;
- }
-
private BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final DateTime nextBillingCycleDate, final BillingPeriod billingPeriod) {
final DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index 78db034..e9385a2 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -19,13 +19,9 @@ package com.ning.billing.invoice.dao;
import java.io.IOException;
import java.net.URL;
-import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.TransactionCallback;
-import org.skife.jdbi.v2.TransactionStatus;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
import com.ning.billing.KillbillTestSuiteWithEmbeddedDB;
import com.ning.billing.config.InvoiceConfig;
@@ -114,7 +110,7 @@ public class InvoiceDaoTestBase extends InvoicingTestBase {
final TagDefinitionDao tagDefinitionDao = new MockTagDefinitionDao();
final TagDao tagDao = new AuditedTagDao(dbi, tagEventBuilder, bus);
final TagUserApi tagUserApi = new DefaultTagUserApi(tagDefinitionDao, tagDao);
- invoiceDao = new DefaultInvoiceDao(dbi, nextBillingDatePoster, tagUserApi);
+ invoiceDao = new DefaultInvoiceDao(dbi, nextBillingDatePoster, tagUserApi, clock);
invoiceDao.test();
invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
index e0f3ea0..df2dd59 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
@@ -29,6 +29,7 @@ import com.ning.billing.invoice.InvoiceTestSuite;
import com.ning.billing.invoice.notification.NextBillingDatePoster;
import com.ning.billing.util.api.TagUserApi;
import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.tag.ControlTagType;
import com.ning.billing.util.tag.Tag;
@@ -49,7 +50,7 @@ public class TestDefaultInvoiceDao extends InvoiceTestSuite {
final TagDefinitionDao tagDefinitionDao = new MockTagDefinitionDao();
final TagDao tagDao = new MockTagDao();
tagUserApi = new DefaultTagUserApi(tagDefinitionDao, tagDao);
- dao = new DefaultInvoiceDao(idbi, poster, tagUserApi);
+ dao = new DefaultInvoiceDao(idbi, poster, tagUserApi, Mockito.mock(Clock.class));
}
@Test(groups = "fast")
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java b/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
index 7989167..b0d75dc 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
@@ -87,7 +87,7 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
final TagDefinitionDao tagDefinitionDao = new MockTagDefinitionDao();
final TagDao tagDao = new MockTagDao();
final TagUserApi tagUserApi = new DefaultTagUserApi(tagDefinitionDao, tagDao);
- final InvoiceDao invoiceDao = new DefaultInvoiceDao(dbi, nextBillingDatePoster, tagUserApi);
+ final InvoiceDao invoiceDao = new DefaultInvoiceDao(dbi, nextBillingDatePoster, tagUserApi, clock);
invoicePaymentApi = new DefaultInvoicePaymentApi(invoiceDao);
context = new TestCallContext("Charge back tests");