killbill-memoizeit

Revert "refactoring in invoice to extact the logic for interval

11/26/2013 12:26:14 AM

Changes

invoice/src/main/java/com/ning/billing/invoice/generator/BillingIntervalDetail.java 140(+0 -140)

invoice/src/test/java/com/ning/billing/invoice/generator/TestBillingIntervalDetail.java 90(+0 -90)

Details

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 8a439b1..ff1591c 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
@@ -60,6 +60,7 @@ public class InvoiceDateUtils {
         return days.divide(daysInPeriod, 2 * NUMBER_OF_DECIMALS, ROUNDING_METHOD);
     }
 
+
     public static BigDecimal calculateProRationBeforeFirstBillingPeriod(final LocalDate startDate, final LocalDate nextBillingCycleDate,
                                                                         final BillingPeriod billingPeriod) {
         final LocalDate previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
@@ -73,10 +74,129 @@ public class InvoiceDateUtils {
         return numberOfMonths / numberOfMonthsInPeriod;
     }
 
+    public static LocalDate calculateLastBillingCycleDateBefore(final LocalDate date, final LocalDate previousBillCycleDate,
+                                                                final int billingCycleDay, final BillingPeriod billingPeriod) {
+        LocalDate proposedDate = previousBillCycleDate;
+
+        int numberOfPeriods = 0;
+        while (!proposedDate.isAfter(date)) {
+            proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+            numberOfPeriods += 1;
+        }
+
+        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+
+        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
+            final int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
+            if (lastDayOfTheMonth < billingCycleDay) {
+                proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
+            } else {
+                proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+            }
+        }
+
+        if (proposedDate.isBefore(previousBillCycleDate)) {
+            // Make sure not to go too far in the past
+            return previousBillCycleDate;
+        } else {
+            return proposedDate;
+        }
+    }
+
+    public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
+                                                      final BillingPeriod billingPeriod) {
+        if (targetDate.isBefore(billCycleDate)) {
+            return billCycleDate;
+        }
+
+        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+        int numberOfPeriods = 0;
+        LocalDate proposedDate = billCycleDate;
+
+        while (!proposedDate.isAfter(targetDate)) {
+            proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            numberOfPeriods += 1;
+        }
+
+        return proposedDate;
+    }
+
+    public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
+                                                      final LocalDate endDate, final BillingPeriod billingPeriod) {
+        if (targetDate.isBefore(endDate)) {
+            if (targetDate.isBefore(billCycleDate)) {
+                return billCycleDate;
+            }
+
+            final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+            int numberOfPeriods = 0;
+            LocalDate proposedDate = billCycleDate;
+
+            while (!proposedDate.isAfter(targetDate)) {
+                proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+                numberOfPeriods += 1;
+            }
+
+            // the current period includes the target date
+            // check to see whether the end date truncates the period
+            if (endDate.isBefore(proposedDate)) {
+                return endDate;
+            } else {
+                return proposedDate;
+            }
+        } else {
+            return endDate;
+        }
+    }
+
+
     public static BigDecimal calculateProRationAfterLastBillingCycleDate(final LocalDate endDate, final LocalDate previousBillThroughDate,
                                                                          final BillingPeriod billingPeriod) {
         // Note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
         final LocalDate nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
         return calculateProrationBetweenDates(previousBillThroughDate, endDate, previousBillThroughDate, nextBillThroughDate);
     }
+
+ /*
+    public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final DateTimeZone accountTimeZone,
+                                                               final int billingCycleDayLocal) {
+        final DateTime tmp = date.toDateTimeAtStartOfDay(accountTimeZone);
+        final DateTime proposedDateTime = calculateBillingCycleDateOnOrAfter(tmp, billingCycleDayLocal);
+
+        return new LocalDate(proposedDateTime, accountTimeZone);
+    }
+
+    public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final DateTimeZone accountTimeZone,
+                                                           final int billingCycleDayLocal) {
+        final DateTime tmp = date.toDateTimeAtStartOfDay(accountTimeZone);
+        final DateTime proposedDateTime = calculateBillingCycleDateAfter(tmp, billingCycleDayLocal);
+
+        return new LocalDate(proposedDateTime, accountTimeZone);
+    }
+    */
+
+    public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final int billingCycleDayLocal) {
+        final int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+        final LocalDate fixedDate;
+        if (billingCycleDayLocal > lastDayOfMonth) {
+            fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth, date.getChronology());
+        } else {
+            fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), billingCycleDayLocal, date.getChronology());
+        }
+
+        LocalDate proposedDate = fixedDate;
+        while (proposedDate.isBefore(date)) {
+            proposedDate = proposedDate.plusMonths(1);
+        }
+        return proposedDate;
+    }
+
+    public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final int billingCycleDayLocal) {
+        LocalDate proposedDate = calculateBillingCycleDateOnOrAfter(date, billingCycleDayLocal);
+        if (date.compareTo(proposedDate) == 0) {
+            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 4491632..5ad6d44 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
@@ -27,8 +27,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.invoice.generator.BillingIntervalDetail;
 
+import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateBillingCycleDateOnOrAfter;
+import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateEffectiveEndDate;
+import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateLastBillingCycleDateBefore;
 import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateNumberOfWholeBillingPeriods;
 import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateProRationAfterLastBillingCycleDate;
 import static com.ning.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod;
@@ -50,8 +52,7 @@ public class InAdvanceBillingMode implements BillingMode {
 
         final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
 
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod);
-
+        final LocalDate firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDayLocal);
 
         // We are not billing for less than a day (we could...)
         if (endDate != null && endDate.equals(startDate)) {
@@ -61,7 +62,7 @@ public class InAdvanceBillingMode implements BillingMode {
         // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do
         // is to charge for that period
         //
-        if (endDate != null && !endDate.isAfter(billingIntervalDetail.getFirstBillingCycleDate())) {
+        if (endDate != null && !endDate.isAfter(firstBillingCycleDate)) {
             final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod);
             final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods);
             results.add(itemData);
@@ -73,11 +74,11 @@ public class InAdvanceBillingMode implements BillingMode {
         // i) The first firstBillingCycleDate is strictly after our start date AND
         // ii) The endDate is is not null and is strictly after our firstBillingCycleDate (previous check)
         //
-        if (billingIntervalDetail.getFirstBillingCycleDate().isAfter(startDate)) {
-            final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, billingIntervalDetail.getFirstBillingCycleDate(), billingPeriod);
+        if (firstBillingCycleDate.isAfter(startDate)) {
+            final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
             if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
                 // Not common - add info in the logs for debugging purposes
-                final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, billingIntervalDetail.getFirstBillingCycleDate(), leadingProRationPeriods);
+                final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods);
                 log.info("Adding pro-ration: {}", itemData);
                 results.add(itemData);
             }
@@ -88,13 +89,18 @@ public class InAdvanceBillingMode implements BillingMode {
         // - If endDate != null and targetDate is after endDate => this is the endDate and will lead to a trailing pro-ration
         // - If not, this is the last billingCycleDate calculation right after the targetDate
         //
-        final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate();
+        final LocalDate effectiveEndDate;
+        if (endDate != null) {
+            effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+        } else {
+            effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+        }
 
         //
         // Based on what we calculated previously, code recompute one more time the numberOfWholeBillingPeriods
         //
-        final LocalDate lastBillingCycleDate = billingIntervalDetail.getLastBillingCycleDate();
-        final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(billingIntervalDetail.getFirstBillingCycleDate(), lastBillingCycleDate, billingPeriod);
+        final LocalDate lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDayLocal, billingPeriod);
+        final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
         final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
 
         for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
@@ -110,7 +116,7 @@ public class InAdvanceBillingMode implements BillingMode {
             }
 
             // Make sure to align the end date with the BCD
-            final LocalDate servicePeriodEndDate = billingIntervalDetail.getFirstBillingCycleDate().plusMonths((i + 1) * numberOfMonthsPerBillingPeriod);
+            final LocalDate servicePeriodEndDate = firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod);
 
             results.add(new RecurringInvoiceItemData(servicePeriodStartDate, servicePeriodEndDate, BigDecimal.ONE));
         }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
index 5728847..2ee9dff 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestInvoiceDateUtils.java
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.generator;
 
 import java.math.BigDecimal;
 
+import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -28,6 +29,22 @@ import com.ning.billing.invoice.InvoiceTestSuiteNoDB;
 public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
 
     @Test(groups = "fast")
+    public void testLastBCDShouldNotBeBeforePreviousBCD() throws Exception {
+        final LocalDate from = new LocalDate("2012-07-16");
+        final LocalDate previousBCD = new LocalDate("2012-08-15");
+        final int bcdLocal = 15;
+        final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(from, previousBCD, bcdLocal, BillingPeriod.MONTHLY);
+        Assert.assertEquals(lastBCD, new LocalDate("2012-08-15"));
+    }
+
+    @Test(groups = "fast")
+    public void testNextBCDShouldNotBeInThePast() throws Exception {
+        final LocalDate from = new LocalDate("2012-07-16");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 15);
+        Assert.assertEquals(to, new LocalDate("2012-08-15"));
+    }
+
+    @Test(groups = "fast")
     public void testProRationAfterLastBillingCycleDate() throws Exception {
         final LocalDate endDate = new LocalDate("2012-06-02");
         final LocalDate previousBillThroughDate = new LocalDate("2012-03-02");
@@ -36,6 +53,67 @@ public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
+    public void testBeforeBCDWithAfter() throws Exception {
+        final LocalDate from = new LocalDate("2012-03-02");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
+        Assert.assertEquals(to, new LocalDate("2012-03-03"));
+    }
+
+    @Test(groups = "fast")
+    public void testEqualBCDWithAfter() throws Exception {
+        final LocalDate from = new LocalDate("2012-03-03");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
+        Assert.assertEquals(to, new LocalDate("2012-04-03"));
+    }
+
+    @Test(groups = "fast")
+    public void testAfterBCDWithAfter() throws Exception {
+        final LocalDate from = new LocalDate("2012-03-04");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
+        Assert.assertEquals(to, new LocalDate("2012-04-03"));
+    }
+
+    @Test(groups = "fast")
+    public void testBeforeBCDWithOnOrAfter() throws Exception {
+        final LocalDate from = new LocalDate("2012-03-02");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
+        Assert.assertEquals(to, new LocalDate("2012-03-03"));
+    }
+
+    @Test(groups = "fast")
+    public void testEqualBCDWithOnOrAfter() throws Exception {
+        final LocalDate from = new LocalDate("2012-03-03");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
+        Assert.assertEquals(to, new LocalDate("2012-03-03"));
+    }
+
+    @Test(groups = "fast")
+    public void testAfterBCDWithOnOrAfter() throws Exception {
+        final LocalDate from = new LocalDate("2012-03-04");
+        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
+        Assert.assertEquals(to, new LocalDate("2012-04-03"));
+    }
+
+    @Test(groups = "fast")
+    public void testEffectiveEndDate() throws Exception {
+        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+        final LocalDate targetDate = new LocalDate(2012, 8, 16);
+        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+        final LocalDate effectiveEndDate = InvoiceDateUtils.calculateEffectiveEndDate(firstBCD, targetDate, billingPeriod);
+        // TODO should that be 2012-09-15?
+        Assert.assertEquals(effectiveEndDate, new LocalDate(2012, 9, 16));
+    }
+
+    @Test(groups = "fast")
+    public void testLastBCD() throws Exception {
+        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
+        final LocalDate effectiveEndDate = new LocalDate(2012, 9, 15);
+        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
+        final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(effectiveEndDate, firstBCD, 16, billingPeriod);
+        Assert.assertEquals(lastBCD, new LocalDate(2012, 8, 16));
+    }
+
+    @Test(groups = "fast")
     public void testCalculateNbOfBillingPeriods() throws Exception {
         final LocalDate firstBCD = new LocalDate(2012, 7, 16);
         final LocalDate lastBCD = new LocalDate(2012, 9, 16);