killbill-aplcache

updates to allow fixed and recurring amounts to be correctly

2/2/2012 4:19:11 PM

Details

diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
index 7c89c41..9665d8d 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/inv_ent/TestBasic.java
@@ -169,26 +169,11 @@ public class TestBasic {
         return ctd;
     }
 
-
-    @Test(groups = "fast", enabled = true)
-    public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
-        testBasePlanComplete(clock.getUTCNow().minusDays(1).getDayOfMonth());
-    }
-
-    @Test(groups = "fast", enabled = true)
-    public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
-        testBasePlanComplete(clock.getUTCNow().getDayOfMonth());
-    }
-
     @Test(groups = "fast", enabled = true)
-    public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
-        testBasePlanComplete(clock.getUTCNow().plusDays(1).getDayOfMonth());
-    }
-
-    private void testBasePlanComplete(int billingDay) throws Exception {
+    public void testBasePlanComplete() throws Exception {
         long DELAY = 5000;
 
-        Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null);
+        Account account = accountUserApi.createAccount(getAccountData(), null, null);
         assertNotNull(account);
 
         SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
@@ -252,8 +237,8 @@ public class TestBasic {
         //
         busHandler.pushExpectedEvent(NextEvent.CHANGE);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
-
         clock.addDeltaFromReality(ctd.getMillis() - clock.getUTCNow().getMillis());
+        //clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
         assertTrue(busHandler.isCompleted(DELAY));
         log.info("testSimple passed fourth busHandler checkpoint.");
 
@@ -290,7 +275,7 @@ public class TestBasic {
     }
 
 
-    protected AccountData getAccountData(final int billCycleDay) {
+    protected AccountData getAccountData() {
         AccountData accountData = new AccountData() {
             @Override
             public String getName() {
@@ -314,7 +299,7 @@ public class TestBasic {
             }
             @Override
             public int getBillCycleDay() {
-                return billCycleDay;
+                return 1;
             }
             @Override
             public Currency getCurrency() {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 84a21ef..95308be 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -83,9 +83,9 @@ public interface InvoiceItemSqlDao extends EntityDao<InvoiceItem> {
                         q.bind("phaseName", item.getPhaseName());
                         q.bind("startDate", item.getStartDate().toDate());
                         q.bind("endDate", item.getEndDate() == null ? null : item.getEndDate().toDate());
-                        q.bind("recurringAmount", item.getRecurringAmount() == null ? BigDecimal.ZERO : item.getRecurringAmount());
-                        q.bind("recurringRate", item.getRecurringRate() == null ? BigDecimal.ZERO : item.getRecurringRate());
-                        q.bind("fixedAmount", item.getFixedAmount() == null ? BigDecimal.ZERO : item.getFixedAmount());
+                        q.bind("recurringAmount", item.getRecurringAmount());
+                        q.bind("recurringRate", item.getRecurringRate());
+                        q.bind("fixedAmount", item.getFixedAmount());
                         q.bind("currency", item.getCurrency().toString());
                     }
                 };
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 7b730ab..4c50391 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -128,18 +128,18 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
     public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
         @Override
         public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
-            BigDecimal amount_invoiced = result.getBigDecimal("amount_invoiced");
-            BigDecimal amount_paid = result.getBigDecimal("amount_paid");
+            BigDecimal amountInvoiced = result.getBigDecimal("amount_invoiced");
+            BigDecimal amountPaid = result.getBigDecimal("amount_paid");
 
-            if (amount_invoiced == null) {
-                amount_invoiced = BigDecimal.ZERO;
+            if (amountInvoiced == null) {
+                amountInvoiced = BigDecimal.ZERO;
             }
 
-            if (amount_paid == null) {
-                amount_paid = BigDecimal.ZERO;
+            if (amountPaid == null) {
+                amountPaid = BigDecimal.ZERO;
             }
 
-            return amount_invoiced.subtract(amount_paid);
+            return amountInvoiced.subtract(amountPaid);
         }
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
index 948638c..0de628c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -51,7 +51,7 @@ public class InvoiceListener {
     private final AccountUserApi accountUserApi;
     private final InvoiceDao invoiceDao;
 
-    private final static boolean VERBOSE_OUTPUT = true;
+    private final static boolean VERBOSE_OUTPUT = false;
 
     @Inject
     public InvoiceListener(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
index 9f21ca6..9a4c13b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
@@ -33,7 +33,11 @@ public class DateRange {
      * @return whether the DateRange contains (inclusively) the DateTime in question
      */
     public boolean contains(DateTime date) {
-        return (!date.isBefore(startDate)) && (!date.isAfter(endDate));
+        if (endDate == null) {
+            return date.compareTo(startDate) == 0;
+        }
+
+        return !date.isBefore(startDate) && !date.isAfter(endDate);
     }
 
     public boolean overlaps(DateRange range) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 9bbc0c7..1467249 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -139,18 +139,17 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
     		BigDecimal numberOfBillingPeriods;
             BigDecimal recurringAmount = null;
+            DateTime billThroughDate = null;
 
             if (recurringRate != null) {
                 numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
                 recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+                BillingMode billingMode = getBillingMode(event.getBillingMode());
+                billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
             }
 
-            BillingMode billingMode = getBillingMode(event.getBillingMode());
-            DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
-            if ((event.getBillingPeriod() == BillingPeriod.NO_BILLING_PERIOD) || (!billThroughDate.isAfter(targetDate.plusMonths(event.getBillingPeriod().getNumberOfMonths())))) {
-                BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
-                addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
-            }
+            BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
+            addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
     	} catch (CatalogApiException e) {
             log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s", 
                     invoiceId.toString(), 
@@ -166,15 +165,15 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
 
             BigDecimal numberOfBillingPeriods;
             BigDecimal recurringAmount = null;
+            DateTime billThroughDate = null;
 
             if (recurringRate != null) {
                 numberOfBillingPeriods = calculateNumberOfBillingPeriods(firstEvent, secondEvent, targetDate);
                 recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
+                BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
+                billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
             }
 
-            BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
-            DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
-
             BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
             addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
     	} catch (CatalogApiException e) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
index 508fab8..b65907e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceItem.java
@@ -165,6 +165,15 @@ public class DefaultInvoiceItem implements InvoiceItem {
     // TODO: deal with error cases
     @Override
     public void subtract(InvoiceItem that) {
+        if (this.endDate == null) {
+            // this is a fixed price item; set the fixed amount to null
+            if (this.fixedAmount.compareTo(that.getFixedAmount()) == 0) {
+                this.fixedAmount = null;
+            }
+
+            return;
+        }
+
         if (this.startDate.equals(that.getStartDate()) && this.endDate.equals(that.getEndDate())) {
             this.startDate = this.endDate;
                 this.recurringAmount = safeSubtract(this.recurringAmount, that.getRecurringAmount());
@@ -210,8 +219,12 @@ public class DefaultInvoiceItem implements InvoiceItem {
 
         if (!this.getCurrency().equals(that.getCurrency())) {return false;}
 
+        if ((this.endDate == null) && (that.getEndDate() == null) && (this.startDate.compareTo(that.getStartDate()) == 0)) {
+            return true;
+        }
+
         DateRange thisDateRange = new DateRange(this.getStartDate(), this.getEndDate());
-        return thisDateRange.contains(that.getStartDate()) && thisDateRange.contains(that.getEndDate());
+        return thisDateRange.contains(that.getStartDate()) && (that.getEndDate() == null || thisDateRange.contains(that.getEndDate()));
     }
 
     private boolean compareNullableBigDecimal(@Nullable BigDecimal value1, @Nullable BigDecimal value2) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index 13ccb60..f35f8b1 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -83,7 +83,9 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
 
             if (fixedAmountNull && (recurringRateNull || recurringAmountZero)) {
                 iterator.remove();
-            } else if (item.getStartDate().compareTo(item.getEndDate()) == 0) {
+            } else if (item.getEndDate() != null && item.getStartDate().compareTo(item.getEndDate()) == 0) {
+                iterator.remove();
+            } else if (item.getFixedAmount() == null && item.getRecurringAmount() == null) {
                 iterator.remove();
             }
         }
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index f483773..c6d4bb6 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -42,8 +42,8 @@ getInvoicesForPayment() ::= <<
   LEFT JOIN invoice_payment_summary ips ON ips.invoice_id = i.id
   LEFT JOIN invoice_item_summary iis ON iis.invoice_id = i.id
   WHERE ((ips.last_payment_date IS NULL) OR (DATEDIFF(:targetDate, ips.last_payment_date) >= :numberOfDays))
-        AND ((ips.total_paid IS NULL) OR (iis.total_amount >= ips.total_paid))
-        AND ((iis.total_amount IS NOT NULL) AND (iis.total_amount > 0))
+        AND ((ips.total_paid IS NULL) OR (iis.amount_invoiced >= ips.total_paid))
+        AND ((iis.amount_invoiced IS NOT NULL) AND (iis.amount_invoiced > 0))
   GROUP BY <invoiceFields("i.")>;
 >>
 
@@ -54,7 +54,8 @@ getById() ::= <<
 >>
 
 getAccountBalance() ::= <<
-  SELECT SUM(iis.total_amount) AS amount_invoiced, SUM(ips.total_paid) AS amount_paid
+  SELECT SUM(iis.amount_invoiced) AS amount_invoiced,
+         SUM(ips.total_paid) AS amount_paid
   FROM invoices i
   LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
   LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
@@ -87,7 +88,7 @@ getUnpaidInvoicesByAccountId() ::= <<
   LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
   WHERE i.account_id = :accountId AND NOT (i.target_date > :upToDate)
   GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
-  HAVING (SUM(iis.total_amount) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
+  HAVING (SUM(iis.amount_invoiced) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
   ORDER BY i.target_date ASC;
 >>
 
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 1cf6f23..59920c0 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -7,9 +7,9 @@ CREATE TABLE invoice_items (
   phase_name varchar(50) NOT NULL,
   start_date datetime NOT NULL,
   end_date datetime NULL,
-  recurring_amount numeric(10,4) NOT NULL,
-  recurring_rate numeric(10,4) NOT NULL,
-  fixed_amount numeric(10,4) NOT NULL,
+  recurring_amount numeric(10,4) NULL,
+  recurring_rate numeric(10,4) NULL,
+  fixed_amount numeric(10,4) NULL,
   currency char(3) NOT NULL,
   PRIMARY KEY(id)
 ) ENGINE=innodb;
@@ -41,12 +41,16 @@ CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, paym
 
 DROP VIEW IF EXISTS invoice_payment_summary;
 CREATE VIEW invoice_payment_summary AS
-SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_attempt_date) AS last_payment_date
+SELECT invoice_id,
+       CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS total_paid,
+       MAX(payment_attempt_date) AS last_payment_date
 FROM invoice_payments
 GROUP BY invoice_id;
 
 DROP VIEW IF EXISTS invoice_item_summary;
 CREATE VIEW invoice_item_summary AS
-select invoice_id, sum(recurring_amount) + SUM(fixed_amount) AS total_amount
-from invoice_items
-group by invoice_id;
+SELECT invoice_id,
+       CASE WHEN SUM(recurring_amount) IS NULL THEN 0 ELSE SUM(recurring_amount) END
+       + CASE WHEN SUM(fixed_amount) IS NULL THEN 0 ELSE SUM(fixed_amount) END AS amount_invoiced
+FROM invoice_items
+GROUP BY invoice_id;