killbill-memoizeit

invoice: round dates for fixed price items We made the granularity

7/8/2012 1:18:01 AM

Details

diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index e3e1f08..e3efac7 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -21,6 +21,7 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.Interval;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
@@ -46,28 +47,28 @@ public class TestIntegration extends TestIntegrationBase {
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
         log.info("Starting testBasePlanCompleteWithBillingDayInPast");
-        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
         testBasePlanComplete(startDate, 31, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
         log.info("Starting testBasePlanCompleteWithBillingDayPresent");
-        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
         testBasePlanComplete(startDate, 1, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exception {
         log.info("Starting testBasePlanCompleteWithBillingDayAlignedWithTrial");
-        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
         testBasePlanComplete(startDate, 2, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
         log.info("Starting testBasePlanCompleteWithBillingDayInFuture");
-        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        final DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
         testBasePlanComplete(startDate, 3, true);
     }
 
@@ -114,7 +115,7 @@ public class TestIntegration extends TestIntegrationBase {
 
         log.info("Starting testRepairChangeBPWithAddonIncluded");
 
-        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 13, 42, 0);
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 13, 42, 0, testTimeZone);
         clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
 
         final Account account = createAccountWithPaymentMethod(getAccountData(25));
@@ -204,7 +205,7 @@ public class TestIntegration extends TestIntegrationBase {
         final UUID accountId = account.getId();
         assertNotNull(account);
 
-        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0, testTimeZone);
         clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
 
         final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "someBundle", context);
@@ -232,7 +233,7 @@ public class TestIntegration extends TestIntegrationBase {
 
         log.info("Starting testWithRecreatePlan");
 
-        final DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        final DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
         final int billingDay = 2;
 
         log.info("Beginning test with BCD of " + billingDay);
@@ -303,7 +304,6 @@ public class TestIntegration extends TestIntegrationBase {
 
     private void testBasePlanComplete(final DateTime initialCreationDate, final int billingDay,
                                       final boolean proRationExpected) throws Exception {
-
         log.info("Beginning test with BCD of " + billingDay);
         final Account account = createAccountWithPaymentMethod(getAccountData(billingDay));
         final UUID accountId = account.getId();
@@ -506,7 +506,7 @@ public class TestIntegration extends TestIntegrationBase {
 
         log.info("Starting testForMultipleRecurringPhases");
 
-        final DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+        final DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0, testTimeZone);
         clock.setDeltaFromReality(initialCreationDate.getMillis() - clock.getUTCNow().getMillis());
 
         final Account account = createAccountWithPaymentMethod(getAccountData(2));
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index cb19fa0..7ea68a6 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -56,6 +56,7 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceService;
 import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.generator.InvoiceDateUtils;
 import com.ning.billing.invoice.model.InvoicingConfiguration;
 import com.ning.billing.junction.plumbing.api.BlockingSubscription;
 import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
@@ -77,6 +78,8 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 public class TestIntegrationBase implements TestListenerStatus {
+    protected static final DateTimeZone testTimeZone = DateTimeZone.UTC;
+
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
     protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
 
@@ -244,9 +247,12 @@ public class TestIntegrationBase implements TestListenerStatus {
 
         boolean wasFound = false;
 
+        // Make sure to round the dates in the comparisons as the invoice items dates are rounded
+        final DateTime roundedStartDate = InvoiceDateUtils.roundDateTimeToDate(startDate, testTimeZone);
+        final DateTime roundedEndDate = InvoiceDateUtils.roundDateTimeToDate(endDate, testTimeZone);
         for (final InvoiceItem item : invoiceItems) {
-            if (item.getStartDate().compareTo(startDate) == 0) {
-                if (item.getEndDate().compareTo(endDate) == 0) {
+            if (item.getStartDate().compareTo(roundedStartDate) == 0) {
+                if (item.getEndDate().compareTo(roundedEndDate) == 0) {
                     if (item.getAmount().compareTo(amount) == 0) {
                         wasFound = true;
                         break;
@@ -263,7 +269,8 @@ public class TestIntegrationBase implements TestListenerStatus {
         assertNotNull(ctd);
         log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
         assertTrue(clock.getUTCNow().isBefore(ctd));
-        assertTrue(ctd.compareTo(chargeThroughDate) == 0);
+        // The CTD is rounded too
+        assertTrue(ctd.compareTo(InvoiceDateUtils.roundDateTimeToDate(chargeThroughDate, testTimeZone)) == 0);
     }
 
     protected SubscriptionData subscriptionDataFromSubscription(final Subscription sub) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 69a8ac4..7d34da2 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -297,44 +297,38 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         return items;
     }
 
-
-    private DateTime roundDateTimeToDate(final DateTime input, final DateTimeZone timeZone) {
-        if (input == null) {
-            return null;
-        }
-        final DateTime tzAdjustedStartDate = input.toDateTime(timeZone);
-        final DateTime roundedStartDate = new DateTime(tzAdjustedStartDate.getYear(), tzAdjustedStartDate.getMonthOfYear(), tzAdjustedStartDate.getDayOfMonth(), 0, 0, timeZone);
-        return roundedStartDate;
-    }
-
+    // Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day)
     List<InvoiceItem> processEvents(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent,
             final DateTime targetDate, final Currency currency) throws InvoiceApiException {
         final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
-        final InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, targetDate, currency);
+
+        final DateTime roundedTargetDate = InvoiceDateUtils.roundDateTimeToDate(targetDate, thisEvent.getTimeZone());
+
+        // Handle fixed price items
+        final InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, roundedTargetDate, currency);
         if (fixedPriceInvoiceItem != null) {
             items.add(fixedPriceInvoiceItem);
         }
 
+        // Handle recurring items
         final BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
         if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
             final BillingMode billingMode = instantiateBillingMode(thisEvent.getBillingMode());
             // Invoice granularity is day; (if not some comparison might fail)
             final DateTime startDate = thisEvent.getEffectiveDate();
-            final DateTime roundedStartDate = roundDateTimeToDate(startDate, thisEvent.getTimeZone());
-            final DateTime roundedTargetDate = roundDateTimeToDate(targetDate, thisEvent.getTimeZone());
+            final DateTime roundedStartDate = InvoiceDateUtils.roundDateTimeToDate(startDate, thisEvent.getTimeZone());
 
-            log.info(String.format("start = %s, rounded = %s, target = %s, in = %s", startDate, roundedStartDate, targetDate, (!roundedStartDate.isAfter(targetDate)) ? "in" : "out"));
-            if (!roundedStartDate.isAfter(targetDate)) {
+            if (!roundedStartDate.isAfter(roundedTargetDate)) {
                 final DateTime endDate = (nextEvent == null) ? null : nextEvent.getEffectiveDate();
 
-                final DateTime roundedEndDate = roundDateTimeToDate(endDate, thisEvent.getTimeZone());
+                final DateTime roundedEndDate = InvoiceDateUtils.roundDateTimeToDate(endDate, thisEvent.getTimeZone());
                 final int billCycleDay = thisEvent.getBillCycleDay();
 
                 final List<RecurringInvoiceItemData> itemData;
                 try {
                     itemData = billingMode.calculateInvoiceItemData(roundedStartDate, roundedEndDate, roundedTargetDate, billCycleDay, billingPeriod);
                 } catch (InvalidDateSequenceException e) {
-                    throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
+                    throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, roundedTargetDate);
                 }
 
                 for (final RecurringInvoiceItemData itemDatum : itemData) {
@@ -370,20 +364,23 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     }
 
     InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent,
-            final DateTime targetDate, final Currency currency) {
-        if (thisEvent.getEffectiveDate().isAfter(targetDate)) {
+                                       final DateTime roundedTargetDate, final Currency currency) {
+        final DateTime roundedStartDate = InvoiceDateUtils.roundDateTimeToDate(thisEvent.getEffectiveDate(), thisEvent.getTimeZone());
+
+        if (roundedStartDate.isAfter(roundedTargetDate)) {
             return null;
         } else {
             final BigDecimal fixedPrice = thisEvent.getFixedPrice();
 
             if (fixedPrice != null) {
                 final Duration duration = thisEvent.getPlanPhase().getDuration();
-                final DateTime endDate = duration.addToDateTime(thisEvent.getEffectiveDate());
+                final DateTime endDate = InvoiceDateUtils.roundDateTimeToDate(duration.addToDateTime(roundedStartDate), thisEvent.getTimeZone());
 
                 return new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(),
-                        thisEvent.getSubscription().getId(),
-                        thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
-                        thisEvent.getEffectiveDate(), endDate, fixedPrice, currency);
+                                                 thisEvent.getSubscription().getId(),
+                                                 thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+                                                 roundedStartDate, endDate,
+                                                 fixedPrice, currency);
             } else {
                 return null;
             }
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
new file mode 100644
index 0000000..5f7f86a
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/InvoiceDateUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.generator;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+public class InvoiceDateUtils {
+    public static DateTime roundDateTimeToDate(final DateTime input, final DateTimeZone timeZone) {
+        if (input == null) {
+            return null;
+        }
+        final DateTime tzAdjustedStartDate = input.toDateTime(timeZone);
+
+        return new DateTime(tzAdjustedStartDate.getYear(), tzAdjustedStartDate.getMonthOfYear(), tzAdjustedStartDate.getDayOfMonth(), 0, 0, timeZone);
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index d41e6c1..974c48c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -536,7 +536,7 @@ public class TestDefaultInvoiceGenerator extends InvoicingTestBase {
         assertNotNull(invoice2);
         assertEquals(invoice2.getNumberOfItems(), 1);
         final FixedPriceInvoiceItem item = (FixedPriceInvoiceItem) invoice2.getInvoiceItems().get(0);
-        assertEquals(item.getStartDate().compareTo(changeDate), 0);
+        assertDatesEqualRounded(item.getStartDate(), changeDate);
     }
 
     @Test(groups = "fast")