killbill-aplcache
Changes
invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg 56(+56 -0)
invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg 42(+21 -21)
Details
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 4e1ffaa..3209c8c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -128,7 +128,8 @@ public enum ErrorCode {
*/
INVOICE_ACCOUNT_ID_INVALID(4001, "No account could be retrieved for id %s"),
INVOICE_INVALID_TRANSITION(4002, "Transition did not contain a subscription id."),
- INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s")
+ INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s"),
+ INVOICE_INVALID_DATE_SEQUENCE(4004, "Date sequence was invalid. Start Date: %s; End Date: %s; Target Date: %s")
;
private int code;
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index 8cadd9a..b0c9961 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -32,25 +32,11 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
String getPhaseName();
- DateTime getStartDate();
+ String getDescription();
- DateTime getEndDate();
-
- BigDecimal getRecurringAmount();
-
- BigDecimal getRecurringRate();
-
- BigDecimal getFixedAmount();
+ BigDecimal getAmount();
Currency getCurrency();
- InvoiceItem asCredit(UUID invoiceId);
-
- int compareTo(InvoiceItem invoiceItem);
-
- void subtract(InvoiceItem that);
-
- boolean duplicates(InvoiceItem that);
-
- boolean cancels(InvoiceItem that);
+ InvoiceItem asCredit();
}
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 19eaad5..947c782 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
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.UUID;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
@@ -41,7 +42,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
private final InvoiceSqlDao invoiceSqlDao;
- private final InvoiceItemSqlDao invoiceItemSqlDao;
+ private final RecurringInvoiceItemSqlDao recurringInvoiceItemSqlDao;
private final InvoicePaymentSqlDao invoicePaymentSqlDao;
private final NextBillingDateNotifier notifier;
private final EntitlementBillingApi entitlementBillingApi;
@@ -52,7 +53,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
final NextBillingDateNotifier notifier, final EntitlementBillingApi entitlementBillingApi) {
this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
- this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+ this.recurringInvoiceItemSqlDao = dbi.onDemand(RecurringInvoiceItemSqlDao.class);
this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
this.eventBus = eventBus;
this.notifier = notifier;
@@ -91,7 +92,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
@Override
public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
- return invoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
+ return recurringInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString());
}
@Override
@@ -117,8 +118,8 @@ public class DefaultInvoiceDao implements InvoiceDao {
Invoice invoice = invoiceDao.getById(invoiceId.toString());
if (invoice != null) {
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+ RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+ List<InvoiceItem> invoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
invoice.addInvoiceItems(invoiceItems);
InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
@@ -144,8 +145,8 @@ public class DefaultInvoiceDao implements InvoiceDao {
invoiceDao.create(invoice);
List<InvoiceItem> invoiceItems = invoice.getInvoiceItems();
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- invoiceItemDao.create(invoiceItems);
+ RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+ recurringInvoiceItemDao.create(invoiceItems);
notifyOfFutureBillingEvents(invoiceSqlDao, invoiceItems);
setChargedThroughDates(invoiceSqlDao, invoiceItems);
@@ -256,9 +257,9 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
private void getInvoiceItemsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
+ RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
for (final Invoice invoice : invoices) {
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+ List<InvoiceItem> invoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
invoice.addInvoiceItems(invoiceItems);
}
}
@@ -274,20 +275,30 @@ public class DefaultInvoiceDao implements InvoiceDao {
private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
for (final InvoiceItem item : invoiceItems) {
- if ((item.getEndDate() != null) &&
- (item.getRecurringAmount() == null || item.getRecurringAmount().compareTo(BigDecimal.ZERO) >= 0)) {
- notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), item.getEndDate());
+ if (item instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+ if ((recurringInvoiceItem.getEndDate() != null) &&
+ (recurringInvoiceItem.getAmount() == null ||
+ recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ notifier.insertNextBillingNotification(dao, item.getSubscriptionId(), recurringInvoiceItem.getEndDate());
}
+ }
+
}
}
private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> invoiceItems) {
for (InvoiceItem item : invoiceItems) {
- if ((item.getEndDate() != null) &&
- (item.getRecurringAmount() == null || item.getRecurringAmount().compareTo(BigDecimal.ZERO) >= 0)) {
- log.info("Setting CTD for invoice item {} to {}", item.getId().toString(), item.getEndDate().toString());
- entitlementBillingApi.setChargedThroughDateFromTransaction(dao, item.getSubscriptionId(), item.getEndDate());
+ if (item instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+ if ((recurringInvoiceItem.getEndDate() != null) &&
+ (recurringInvoiceItem.getAmount() == null ||
+ recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ log.info("Setting CTD for invoice item {} to {}", recurringInvoiceItem.getId().toString(), recurringInvoiceItem.getEndDate().toString());
+ entitlementBillingApi.setChargedThroughDateFromTransaction(dao, recurringInvoiceItem.getSubscriptionId(), recurringInvoiceItem.getEndDate());
+ }
}
+
}
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
new file mode 100644
index 0000000..ed6ffe0
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
@@ -0,0 +1,93 @@
+package com.ning.billing.invoice.dao;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.entity.EntityDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(FixedPriceInvoiceItemSqlDao.FixedPriceInvoiceItemMapper.class)
+public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+ @SqlQuery
+ List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
+
+ @SqlQuery
+ List<InvoiceItem> getInvoiceItemsByAccount(@Bind("accountId") final String accountId);
+
+ @SqlQuery
+ List<InvoiceItem> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId);
+
+ @Override
+ @SqlUpdate
+ void create(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+ @Override
+ @SqlUpdate
+ void update(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+ @SqlBatch
+ void create(@FixedPriceInvoiceItemBinder final List<FixedPriceInvoiceItem> items);
+
+ @BindingAnnotation(FixedPriceInvoiceItemBinder.FixedPriceInvoiceItemBinderFactory.class)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.PARAMETER})
+ public @interface FixedPriceInvoiceItemBinder {
+ public static class FixedPriceInvoiceItemBinderFactory implements BinderFactory {
+ public Binder build(Annotation annotation) {
+ return new Binder<FixedPriceInvoiceItemBinder, FixedPriceInvoiceItem>() {
+ public void bind(SQLStatement q, FixedPriceInvoiceItemBinder bind, FixedPriceInvoiceItem item) {
+ q.bind("id", item.getId().toString());
+ q.bind("invoiceId", item.getInvoiceId().toString());
+ q.bind("subscriptionId", item.getSubscriptionId().toString());
+ q.bind("planName", item.getPlanName());
+ q.bind("phaseName", item.getPhaseName());
+ q.bind("date", item.getDate().toDate());
+ q.bind("amount", item.getAmount());
+ q.bind("currency", item.getCurrency().toString());
+ }
+ };
+ }
+ }
+ }
+
+ public static class FixedPriceInvoiceItemMapper implements ResultSetMapper<FixedPriceInvoiceItem> {
+ @Override
+ public FixedPriceInvoiceItem map(int index, ResultSet result, StatementContext context) throws SQLException {
+ UUID id = UUID.fromString(result.getString("id"));
+ UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+ UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+ String planName = result.getString("plan_name");
+ String phaseName = result.getString("phase_name");
+ DateTime date = new DateTime(result.getTimestamp("date"));
+ BigDecimal amount = result.getBigDecimal("amount");
+ Currency currency = Currency.valueOf(result.getString("currency"));
+
+ return new FixedPriceInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName, date,
+ amount, currency);
+ }
+ }
+}
\ No newline at end of file
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 174b44d..8cf0195 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -69,16 +69,24 @@ public class InvoiceListener {
@Subscribe
public void handleSubscriptionTransition(final SubscriptionTransition transition) {
- processSubscription(transition);
+ try {
+ processSubscription(transition);
+ } catch (InvoiceApiException e) {
+ log.error(e.getMessage());
+ }
}
@Subscribe
public void handleNextBillingDateEvent(final NextBillingDateEvent event) {
// STEPH should we use the date of the event instead?
- processSubscription(event.getSubscriptionId(), clock.getUTCNow());
+ try {
+ processSubscription(event.getSubscriptionId(), clock.getUTCNow());
+ } catch (InvoiceApiException e) {
+ log.error(e.getMessage());
+ }
}
- private void processSubscription(final SubscriptionTransition transition) {
+ private void processSubscription(final SubscriptionTransition transition) throws InvoiceApiException {
UUID subscriptionId = transition.getSubscriptionId();
DateTime targetDate = transition.getEffectiveTransitionTime();
log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
@@ -86,7 +94,7 @@ public class InvoiceListener {
processSubscription(subscriptionId, targetDate);
}
- private void processSubscription(final UUID subscriptionId, final DateTime targetDate) {
+ private void processSubscription(final UUID subscriptionId, final DateTime targetDate) throws InvoiceApiException {
if (subscriptionId == null) {
log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
return;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
index 0ce3b52..da95559 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingEventSet.java
@@ -31,9 +31,7 @@ public class BillingEventSet extends ArrayList<BillingEvent> {
addAll(events);
}
- public BillingEvent getLast() {
- if (this.size() == 0) {return null;}
-
- return this.get(this.size() - 1);
+ public boolean isLast(final BillingEvent event) {
+ return (super.indexOf(event) == size() - 1);
}
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
index 71c4765..e197feb 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
@@ -19,14 +19,17 @@ package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.BillingPeriod;
import org.joda.time.DateTime;
-import java.math.BigDecimal;
+import java.util.List;
public interface BillingMode {
- BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+ List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+ List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
- BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
- DateTime calculateEffectiveEndDate(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
-
- DateTime calculateEffectiveEndDate(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
+// BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+//
+// BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+//
+// DateTime calculateEffectiveEndDate(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
+//
+// DateTime calculateEffectiveEndDate(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
index ffdf806..55c646c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
@@ -21,64 +21,62 @@ import org.joda.time.DateTime;
import java.math.BigDecimal;
-public abstract class BillingModeBase implements BillingMode {
- @Override
- public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
- if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
- if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
- if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
- return BigDecimal.ZERO;
- }
-
- BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- DateTime endBillCycleDate;
- BigDecimal trailingProRation;
- BigDecimal numberOfBillingPeriods;
-
- DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
- endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
- numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
- trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
-
- return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
- }
-
- @Override
- public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
- if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
- BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
- BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
- return precedingProRation.add(numberOfBillingPeriods);
- }
-
- boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
- return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
- }
-
- public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
-
- protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
-
- protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
-
- protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
+public abstract class BillingModeBase {
+// public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+// if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+// if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+// if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
+// return BigDecimal.ZERO;
+// }
+//
+// BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+// DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+// DateTime endBillCycleDate;
+// BigDecimal trailingProRation;
+// BigDecimal numberOfBillingPeriods;
+//
+// DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
+// endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+// numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+// trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
+//
+// return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
+// }
+//
+// public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+// if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+// BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+// DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+// DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+// BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+// return precedingProRation.add(numberOfBillingPeriods);
+// }
+//
+// boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
+// return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
+// }
+//
+// public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
+//
+// protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
+//
+// protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
+//
+// protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
}
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 c83be80..b2e3e6e 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
@@ -17,243 +17,234 @@
package com.ning.billing.invoice.model;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
+import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.Days;
-import org.joda.time.Duration;
-import org.joda.time.Period;
-import org.joda.time.format.ISODateTimeFormat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.BillingModeType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.MutableDateTime;
import javax.annotation.Nullable;
public class DefaultInvoiceGenerator implements InvoiceGenerator {
- private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
+ private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
+ private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+ //private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
@Override
public Invoice generateInvoice(final UUID accountId, final BillingEventSet events,
- @Nullable final InvoiceItemList existingItems, final DateTime targetDate,
- final Currency targetCurrency) {
- if (events == null) {
+ @Nullable final List<InvoiceItem> items, final DateTime targetDate,
+ final Currency targetCurrency) throws InvoiceApiException {
+ if ((events == null) || (events.size() == 0)) {
return null;
}
- if (events.size() == 0) {
- return null;
+ Collections.sort(events);
+
+ List<InvoiceItem> existingItems = null;
+ if (items != null) {
+ existingItems = new ArrayList<InvoiceItem>(items);
+ Collections.sort(existingItems);
}
DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
- InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
- InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
+ UUID invoiceId = invoice.getId();
+ List<InvoiceItem> proposedItems = generateInvoiceItems(invoiceId, events, targetDate, targetCurrency);
- if (itemsToPost.size() == 0) {
+ if (existingItems != null) {
+ removeCancellingInvoiceItems(existingItems);
+ removeDuplicatedInvoiceItems(proposedItems, existingItems);
+
+ for (InvoiceItem existingItem : existingItems) {
+ if (existingItem instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
+ proposedItems.add(recurringItem.asCredit());
+ }
+ }
+ }
+
+ if (proposedItems == null || proposedItems.size() == 0) {
return null;
} else {
- invoice.addInvoiceItems(itemsToPost);
+ invoice.addInvoiceItems(proposedItems);
return invoice;
}
}
- private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems,
- final InvoiceItemList existingInvoiceItems) {
- if ((existingInvoiceItems == null) || (existingInvoiceItems.size() == 0)) {
- return currentInvoiceItems;
- }
-
- InvoiceItemList currentItems = new InvoiceItemList();
- for (final InvoiceItem item : currentInvoiceItems) {
- currentItems.add(new DefaultInvoiceItem(item, invoiceId));
+ /*
+ * removes all matching items from both submitted collections
+ */
+ private void removeDuplicatedInvoiceItems(final List<InvoiceItem> proposedItems,
+ final List<InvoiceItem> existingInvoiceItems) {
+ Iterator<InvoiceItem> proposedItemIterator = proposedItems.iterator();
+ while (proposedItemIterator.hasNext()) {
+ InvoiceItem proposedItem = proposedItemIterator.next();
+
+ if (existingInvoiceItems.contains(proposedItem)) {
+ existingInvoiceItems.remove(proposedItem);
+ proposedItemIterator.remove();
+ }
}
+ }
- // STEPH why clone? Why cast?
- InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
-
- Collections.sort(currentItems);
- Collections.sort(existingItems);
-
- for (final InvoiceItem currentItem : currentItems) {
- Iterator<InvoiceItem> it = existingItems.iterator();
+ private void removeCancellingInvoiceItems(final List<InvoiceItem> items) {
+ List<UUID> itemsToRemove = new ArrayList<UUID>();
- // see if there are any existing items that are covered by the current item
- while (it.hasNext()) {
- InvoiceItem existingItem = it.next();
- // STEPH this is more like 'contained' that 'duplicates'
- if (currentItem.duplicates(existingItem)) {
- currentItem.subtract(existingItem);
- it.remove();
+ for (InvoiceItem item1 : items) {
+ if (item1 instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item1;
+ if (recurringInvoiceItem.reversesItem()) {
+ itemsToRemove.add(recurringInvoiceItem.getId());
+ itemsToRemove.add(recurringInvoiceItem.getReversedItemId());
}
}
}
- // remove cancelling pairs of invoice items
- existingItems.removeCancellingPairs();
-
- // add existing items that aren't covered by current items as credit items
- for (final InvoiceItem existingItem : existingItems) {
- // STEPH do we really want to credit if that has not been paid yet?
- currentItems.add(existingItem.asCredit(existingItem.getInvoiceId()));
+ Iterator<InvoiceItem> iterator = items.iterator();
+ while (iterator.hasNext()) {
+ InvoiceItem item = iterator.next();
+ if (itemsToRemove.contains(item.getId())) {
+ iterator.remove();
+ }
}
-
- currentItems.cleanupDuplicatedItems();
-
- return currentItems;
}
- private InvoiceItemList generateInvoiceItems(final BillingEventSet events, final UUID invoiceId,
- final DateTime targetDate, final Currency targetCurrency) {
- InvoiceItemList items = new InvoiceItemList();
+ private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final BillingEventSet events,
+ final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+ List<InvoiceItem> items = new ArrayList<InvoiceItem>();
- // sort events; this relies on the sort order being by subscription id then start date
- Collections.sort(events);
-
- // for each event, process it either as a terminated event (if there's a subsequent event)
- // ...or as a non-terminated event (if no subsequent event exists)
- for (int i = 0; i < (events.size() - 1); i++) {
+ for (int i = 0; i < events.size(); i++) {
BillingEvent thisEvent = events.get(i);
- BillingEvent nextEvent = events.get(i + 1);
-
-
- if (thisEvent.getSubscription().getId().equals(nextEvent.getSubscription().getId())) {
- processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
- } else {
- processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
+ BillingEvent nextEvent = events.isLast(thisEvent) ? null : events.get(i + 1);
+ if (nextEvent != null) {
+ nextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null;
}
- }
- // process the last item in the event set
- if (events.size() > 0) {
- processEvent(invoiceId, events.getLast(), items, targetDate, targetCurrency);
+ items.addAll(processEvents(invoiceId, thisEvent, nextEvent, targetDate, currency));
}
return items;
}
- private void processEvent(final UUID invoiceId, final BillingEvent event, final InvoiceItemList items,
- final DateTime targetDate, final Currency targetCurrency) {
- try {
- // STEPH should not that check apply to next method as well?
- if (event.getEffectiveDate().compareTo(targetDate) > 0) {
- return;
- }
-
- BigDecimal recurringRate = event.getRecurringPrice() == null ? null : event.getRecurringPrice().getPrice(targetCurrency);
- BigDecimal fixedPrice = event.getFixedPrice() == null ? null : event.getFixedPrice().getPrice(targetCurrency);
+ private List<InvoiceItem> processEvents(final UUID invoiceId, final BillingEvent thisEvent, final BillingEvent nextEvent,
+ final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+ List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+ InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, thisEvent, targetDate, currency);
+ if (fixedPriceInvoiceItem != null) {
+ items.add(fixedPriceInvoiceItem);
+ }
- BigDecimal numberOfBillingPeriods;
- BigDecimal recurringAmount = null;
- DateTime billThroughDate;
+ BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
+ if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+ BillingMode billingMode = instantiateBillingMode(thisEvent.getBillingMode());
+ DateTime startDate = thisEvent.getEffectiveDate();
+ DateTime endDate = (nextEvent == null) ? null : nextEvent.getEffectiveDate();
+ int billCycleDay = thisEvent.getBillCycleDay();
+
+ List<RecurringInvoiceItemData> itemData;
+ try {
+ itemData = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, billCycleDay, billingPeriod);
+ } catch (InvalidDateSequenceException e) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
+ }
- if (recurringRate == null) {
- billThroughDate = event.getPlanPhase().getDuration().addToDateTime(event.getEffectiveDate());
- } else {
- numberOfBillingPeriods = calculateNumberOfBillingPeriods(event, targetDate);
- recurringAmount = numberOfBillingPeriods.multiply(recurringRate);
- BillingMode billingMode = getBillingMode(event.getBillingMode());
- billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
+ for (RecurringInvoiceItemData itemDatum : itemData) {
+ InternationalPrice price = thisEvent.getRecurringPrice();
+ if (price != null) {
+ BigDecimal rate;
+
+ try {
+ rate = thisEvent.getRecurringPrice().getPrice(currency);
+ } catch (CatalogApiException e) {
+ throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+ }
+
+ BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
+
+ RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+ thisEvent.getPlan().getName(),
+ thisEvent.getPlanPhase().getName(),
+ itemDatum.getStartDate(), itemDatum.getEndDate(),
+ amount, rate, currency);
+ items.add(recurringItem);
+ }
}
+ }
- BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(event.getPlanPhase().getName()) ? null : fixedPrice;
+ return items;
+ }
- // STEPH don't we also need to check for if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0)
- addInvoiceItem(invoiceId, items, event, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
- } catch (CatalogApiException e) {
- // STEPH same remark for catalog exception.
- log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
- invoiceId.toString(),
- ISODateTimeFormat.basicDateTime().print(event.getEffectiveDate())), e);
+ private BillingMode instantiateBillingMode(BillingModeType billingMode) {
+ switch (billingMode) {
+ case IN_ADVANCE:
+ return new InAdvanceBillingMode();
+ default:
+ throw new UnsupportedOperationException();
}
}
- private void processEvents(final UUID invoiceId, final BillingEvent firstEvent, final BillingEvent secondEvent,
- final InvoiceItemList items, final DateTime targetDate, final Currency targetCurrency) {
- try {
- BigDecimal recurringRate = firstEvent.getRecurringPrice() == null ? null : firstEvent.getRecurringPrice().getPrice(targetCurrency);
- BigDecimal fixedPrice = firstEvent.getFixedPrice() == null ? null : firstEvent.getFixedPrice().getPrice(targetCurrency);
-
- BigDecimal numberOfBillingPeriods;
- BigDecimal recurringAmount = null;
- DateTime billThroughDate;
-
- if (recurringRate == null) {
- // since it's fixed price only, the following event dictates the end date, regardless of when it takes place
- billThroughDate = secondEvent.getEffectiveDate();
- } else {
- 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());
- }
-
- if (Days.daysBetween(firstEvent.getEffectiveDate(), billThroughDate).getDays() > 0) {
- BigDecimal effectiveFixedPrice = items.hasInvoiceItemForPhase(firstEvent.getPlanPhase().getName()) ? null : fixedPrice;
- addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, recurringAmount, recurringRate, effectiveFixedPrice, targetCurrency);
+ private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final BillingEvent thisEvent,
+ final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+ if (thisEvent.getEffectiveDate().isAfter(targetDate)) {
+ return null;
+ } else {
+ FixedPriceInvoiceItem fixedPriceInvoiceItem = null;
+
+ if (thisEvent.getFixedPrice() != null) {
+ try {
+ BigDecimal fixedPrice = thisEvent.getFixedPrice().getPrice(currency);
+ fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+ thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+ thisEvent.getEffectiveDate(), fixedPrice, currency);
+ } catch (CatalogApiException e) {
+ throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+ }
}
- } catch (CatalogApiException e) {
- // STEPH That needs to be thrown so we stop that invoice generation
- log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
- invoiceId.toString(),
- ISODateTimeFormat.basicDateTime().print(firstEvent.getEffectiveDate())), e);
+ return fixedPriceInvoiceItem;
}
}
- private void addInvoiceItem(final UUID invoiceId, final InvoiceItemList items, final BillingEvent event,
- final DateTime billThroughDate, final BigDecimal amount, final BigDecimal rate,
- final BigDecimal fixedAmount, final Currency currency) {
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(),
- event.getPlan().getName(), event.getPlanPhase().getName(), event.getEffectiveDate(),
- billThroughDate, amount, rate, fixedAmount, currency);
- items.add(item);
- System.out.println(item);
- }
-
- private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent event, final DateTime targetDate){
- BillingMode billingMode = getBillingMode(event.getBillingMode());
- DateTime startDate = event.getEffectiveDate();
- int billingCycleDay = event.getBillCycleDay();
- BillingPeriod billingPeriod = event.getBillingPeriod();
-
- try {
- return billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
- } catch (InvalidDateSequenceException e) {
- // TODO: Jeff -- log issue
- return BigDecimal.ZERO;
- }
- }
+ // assumption: startDate is in the user's time zone
+ private DateTime calculateSegmentEndDate(final DateTime startDate, final DateTime nextEndDate,
+ final int billCycleDay, final BillingPeriod billingPeriod) {
+ int dayOfMonth = startDate.getDayOfMonth();
+ int maxDayOfMonth = startDate.dayOfMonth().getMaximumValue();
- private BigDecimal calculateNumberOfBillingPeriods(final BillingEvent firstEvent, final BillingEvent secondEvent,
- final DateTime targetDate) {
- BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
- DateTime startDate = firstEvent.getEffectiveDate();
- int billingCycleDay = firstEvent.getBillCycleDay();
- BillingPeriod billingPeriod = firstEvent.getBillingPeriod();
+ DateTime nextBillingDate;
- DateTime endDate = secondEvent.getEffectiveDate();
+ // if the start date is not on the bill cycle day, move it to the nearest following date that works
+ if ((billCycleDay > maxDayOfMonth) || (dayOfMonth == billCycleDay)) {
+ nextBillingDate = startDate.plusMonths(billingPeriod.getNumberOfMonths());
+ } else {
+ MutableDateTime proposedDate = startDate.toMutableDateTime();
- try {
- return billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
- } catch (InvalidDateSequenceException e) {
- // TODO: Jeff -- log issue
- return BigDecimal.ZERO;
+ if (dayOfMonth < billCycleDay) {
+ // move the end date forward to the bill cycle date (same month)
+ int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+ nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+ } else {
+ // go to the next month
+ proposedDate = proposedDate.monthOfYear().add(1);
+ maxDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
+ int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+ nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+ }
}
- }
- private BillingMode getBillingMode(final BillingModeType billingModeType) {
- switch (billingModeType) {
- case IN_ADVANCE:
- return new InAdvanceBillingMode();
- default:
- return null;
- }
+ return nextBillingDate.isAfter(nextEndDate) ? nextEndDate : nextBillingDate;
}
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
new file mode 100644
index 0000000..2e20fce
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -0,0 +1,240 @@
+/*
+* Copyright 2010-2011 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.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class FixedPriceInvoiceItem implements InvoiceItem {
+ private final UUID id;
+ private final UUID invoiceId;
+ private final UUID subscriptionId;
+ private final String planName;
+ private final String phaseName;
+ private DateTime date;
+ private BigDecimal amount;
+ private final Currency currency;
+
+ public FixedPriceInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime date, BigDecimal amount, Currency currency) {
+ this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName,
+ date, amount, currency);
+ }
+
+ public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime date, BigDecimal amount, Currency currency) {
+ this.id = id;
+ this.invoiceId = invoiceId;
+ this.subscriptionId = subscriptionId;
+ this.planName = planName;
+ this.phaseName = phaseName;
+ this.date = date;
+ this.amount = amount;
+ this.currency = currency;
+ }
+
+// public FixedPriceInvoiceItem(FixedPriceInvoiceItem that, UUID invoiceId) {
+// this.id = UUID.randomUUID();
+// this.invoiceId = invoiceId;
+// this.subscriptionId = that.getSubscriptionId();
+// this.planName = that.getPlanName();
+// this.phaseName = that.getPhaseName();
+// this.date = that.getDate();
+// this.amount = that.getAmount();
+// this.currency = that.getCurrency();
+// }
+
+ @Override
+ public InvoiceItem asCredit() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ @Override
+ public String getPlanName() {
+ return planName;
+ }
+
+ @Override
+ public String getPhaseName() {
+ return phaseName;
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("%s (fixed price) on %s", getPhaseName(), getDate().toString());
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = subscriptionId != null ? subscriptionId.hashCode() : 0;
+ result = 31 * result + (planName != null ? planName.hashCode() : 0);
+ result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
+ result = 31 * result + (date != null ? date.hashCode() : 0);
+ result = 31 * result + (amount != null ? amount.hashCode() : 0);
+ result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ return result;
+ }
+
+ public DateTime getDate() {
+ return date;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public int compareTo(InvoiceItem item) {
+ if (!(item instanceof FixedPriceInvoiceItem)) {
+ return 1;
+ }
+
+ FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+
+ if (compareSubscriptions == 0) {
+ return getDate().compareTo(that.getDate());
+ } else {
+ return compareSubscriptions;
+ }
+ }
+
+// @Override
+// public void subtract(InvoiceItem that) {
+// // for now, do nothing -- fixed price items aren't subtracted
+// return;
+// }
+//
+// @Override
+// public boolean duplicates(InvoiceItem item) {
+// if (!(item instanceof FixedPriceInvoiceItem)) {return false;}
+//
+// FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+// if (!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+//
+// if (!this.planName.equals(that.getPlanName())) {return false;}
+// if (!this.phaseName.equals(that.getPhaseName())) {return false;}
+//
+// if (!compareNullableBigDecimal(this.getAmount(), that.getAmount())) {return false;}
+//
+// if (!this.getCurrency().equals(that.getCurrency())) {return false;}
+//
+// return (this.date.compareTo(that.getDate()) == 0);
+// }
+//
+// private boolean compareNullableBigDecimal(@Nullable BigDecimal value1, @Nullable BigDecimal value2) {
+// if ((value1 == null) && (value2 != null)) {return false;}
+// if ((value1 != null) && (value2 == null)) {return false;}
+//
+// if ((value1 != null) && (value2 != null)) {
+// if (value1.compareTo(value2) != 0) {return false;}
+// }
+//
+// return true;
+// }
+//
+// /**
+// * indicates whether the supplied item is a cancelling item for this item
+// * @param item the InvoiceItem to be examined
+// * @return true if the two invoice items cancel each other out (same subscription, same date range, sum of amounts = 0)
+// */
+// @Override
+// public boolean cancels(InvoiceItem item) {
+// if (!(item instanceof FixedPriceInvoiceItem)) {return false;}
+//
+// FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+//
+// if(!this.getSubscriptionId().equals(that.getSubscriptionId())) {return false;}
+// if(!this.getDate().equals(that.getDate())) {return false;}
+//
+// if (!safeCheckForZeroSum(this.getAmount(), that.getAmount())) {return false;}
+//
+// if(!this.getCurrency().equals(that.getCurrency())) {return false;}
+//
+// return true;
+// }
+//
+// private boolean safeCheckForZeroSum(final BigDecimal value1, final BigDecimal value2) {
+// if ((value1 == null) && (value2 == null)) {return true;}
+// if ((value1 == null) ^ (value2 == null)) {return false;}
+// return (value1.add(value2).compareTo(BigDecimal.ZERO) == 0);
+// }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+ sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+ sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+ sb.append("planName = ").append(planName).append(", ");
+ sb.append("phaseName = ").append(phaseName).append(", ");
+ sb.append("date = ").append(date.toString()).append(", ");
+
+ sb.append("amount = ");
+ if (amount == null) {
+ sb.append("null");
+ } else {
+ sb.append(amount.toString());
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) o;
+
+ if (amount != null ? !amount.equals(that.amount) : that.amount != null) return false;
+ if (currency != that.currency) return false;
+ if (date != null ? !date.equals(that.date) : that.date != null) return false;
+ if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) return false;
+ if (planName != null ? !planName.equals(that.planName) : that.planName != null) return false;
+ if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+ return false;
+
+ return true;
+ }
+}
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 b8f5c42..864aecf 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
@@ -23,111 +23,123 @@ import org.joda.time.Months;
import org.joda.time.MutableDateTime;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
-public class InAdvanceBillingMode extends BillingModeBase {
- private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+public class InAdvanceBillingMode implements BillingMode {
+ private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
@Override
- public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- return calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
- }
+ public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate, final DateTime endDate,
+ final DateTime targetDate, final int billingCycleDay,
+ final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+ if (endDate == null) {
+ return calculateInvoiceItemData(startDate, targetDate, billingCycleDay, billingPeriod);
+ }
- @Override
- public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- return calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
- }
+ if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+ if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
- @Override
- protected BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
- int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
- BigDecimal numberOfMonthsInPeriod = new BigDecimal(billingPeriod.getNumberOfMonths());
- return new BigDecimal(numberOfMonths).divide(numberOfMonthsInPeriod, 0, ROUNDING_METHOD);
- }
-
- @Override
- protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
- int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+ List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
+ // beginning from the start date, find the first billing date
+ DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- MutableDateTime tmp = date.toMutableDateTime();
- if (billingCycleDay > lastDayOfMonth) {
- tmp.setDayOfMonth(lastDayOfMonth);
- } else {
- tmp.setDayOfMonth(billingCycleDay);
+ // add pro-ration item if needed
+ if (firstBillingCycleDate.isAfter(startDate)) {
+ BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+ if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+ }
}
- DateTime proposedDate = tmp.toDateTime();
- while (proposedDate.isBefore(date)) {
- // STEPH could be an annual ?
- proposedDate = proposedDate.plusMonths(1);
- }
- return proposedDate;
- }
+ // add one item per billing period
+ DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+ DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+ int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+ int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
- @Override
- protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime proposedDate = billingCycleDate;
+ for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+ results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+ firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+ }
- while (!proposedDate.isAfter(date)) {
- proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
-
- if (proposedDate.dayOfMonth().get() != billingCycleDay) {
- int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
-
- if (lastDayOfMonth < billingCycleDay) {
- proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth,
- proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
- proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
- } else {
- proposedDate = new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
- proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
- proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
- }
+ // check to see if a trailing pro-ration amount is needed
+ if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+ BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+ if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
}
}
- return proposedDate;
+ return results;
}
@Override
- protected DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime proposedDate = previousBillCycleDate;
- proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+ public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate,
+ final DateTime targetDate, final int billingCycleDay,
+ final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+ if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
- if (!proposedDate.isBefore(date)) {return previousBillCycleDate;}
+ List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
- while (proposedDate.isBefore(date)) {
- proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+ // beginning from the start date, find the first billing date
+ DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+
+ // add pro-ration item if needed
+ if (firstBillingCycleDate.isAfter(startDate)) {
+ BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+ if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+ }
}
- proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+ // add one item per billing period
+ DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+ DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+ int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+ int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
- if (proposedDate.dayOfMonth().get() < billingCycleDay) {
- int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
- if (lastDayOfTheMonth < billingCycleDay) {
- return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth,
- proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
- proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
- } else {
- return new DateTime(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay,
- proposedDate.getHourOfDay(), proposedDate.getMinuteOfHour(),
- proposedDate.getSecondOfMinute(), proposedDate.getMillisOfSecond());
+ for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+ results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+ firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+ }
+
+ // check to see if a trailing pro-ration amount is needed
+ if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+ BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+ if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
}
+ }
+
+ return results;
+ }
+
+ private DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
+ int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+ MutableDateTime tmp = date.toMutableDateTime();
+ if (billingCycleDay > lastDayOfMonth) {
+ tmp.setDayOfMonth(lastDayOfMonth);
} else {
- return proposedDate;
+ tmp.setDayOfMonth(billingCycleDay);
}
+ DateTime proposedDate = tmp.toDateTime();
+
+ while (proposedDate.isBefore(date)) {
+ // STEPH could be an annual ?
+ proposedDate = proposedDate.plusMonths(1);
+ }
+ return proposedDate;
}
- @Override
- protected BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+ private BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, DateTime nextBillingCycleDate, final BillingPeriod billingPeriod) {
DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
- if (daysBetween == 0) {
+ if (daysBetween <= 0) {
return BigDecimal.ZERO;
}
@@ -137,42 +149,86 @@ public class InAdvanceBillingMode extends BillingModeBase {
return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
}
- @Override
- protected BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
- // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
- DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
- BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
-
- BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
-
- return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+ private int calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
+ int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
+ int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+ return numberOfMonths / numberOfMonthsInPeriod;
}
- @Override
- protected DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
+ private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
if (targetDate.isBefore(endDate)) {
if (targetDate.isBefore(billCycleDate)) {
return billCycleDate;
}
int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
- DateTime startOfPeriod = billCycleDate;
- DateTime startOfNextPeriod = billCycleDate.plusMonths(numberOfMonthsInPeriod);
+ int numberOfPeriods = 0;
+ DateTime proposedDate = billCycleDate;
- while (isNotBetween(targetDate, startOfPeriod, startOfNextPeriod)) {
- startOfPeriod = startOfNextPeriod;
- startOfNextPeriod = startOfPeriod.plusMonths(numberOfMonthsInPeriod);
+ 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(startOfNextPeriod)) {
+ if (endDate.isBefore(proposedDate)) {
return endDate;
} else {
- return startOfNextPeriod;
+ return proposedDate;
}
} else {
return endDate;
}
}
+
+ private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, BillingPeriod billingPeriod) {
+ if (targetDate.isBefore(billCycleDate)) {
+ return billCycleDate;
+ }
+
+ int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+ int numberOfPeriods = 0;
+ DateTime proposedDate = billCycleDate;
+
+ while (!proposedDate.isAfter(targetDate)) {
+ proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+ numberOfPeriods += 1;
+ }
+
+ return proposedDate;
+ }
+
+ private DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
+ DateTime 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) {
+ int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
+ if (lastDayOfTheMonth < billingCycleDay) {
+ return new MutableDateTime(proposedDate).dayOfMonth().set(lastDayOfTheMonth).toDateTime();
+ } else {
+ return new MutableDateTime(proposedDate).dayOfMonth().set(billingCycleDay).toDateTime();
+ }
+ } else {
+ return proposedDate;
+ }
+ }
+
+ private BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
+ // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
+ DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
+ BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
+
+ BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
+
+ return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+ }
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 626993e..6bc6c9d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -18,11 +18,14 @@ package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
import org.joda.time.DateTime;
import javax.annotation.Nullable;
+import java.util.List;
import java.util.UUID;
public interface InvoiceGenerator {
- public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+ public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
}
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 f35f8b1..683bbaf 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
@@ -18,13 +18,12 @@ package com.ning.billing.invoice.model;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import com.ning.billing.invoice.api.InvoiceItem;
public class InvoiceItemList extends ArrayList<InvoiceItem> {
private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
- private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+ private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
public InvoiceItemList() {
super();
@@ -40,64 +39,50 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
for (final InvoiceItem item : this) {
- if (item.getRecurringAmount() != null) {
- total = total.add(item.getRecurringAmount());
- }
-
- if (item.getFixedAmount() != null) {
- total = total.add(item.getFixedAmount());
+ if (item.getAmount() != null) {
+ total = total.add(item.getAmount());
}
}
return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
}
- public void removeCancellingPairs() {
- List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
-
- for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
- for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
- InvoiceItem firstItem = this.get(firstItemIndex);
- InvoiceItem secondItem = this.get(secondItemIndex);
- if (firstItem.cancels(secondItem)) {
- itemsToRemove.add(firstItem);
- itemsToRemove.add(secondItem);
- }
- }
- }
-
- this.removeAll(itemsToRemove);
- }
-
- /*
- * removes items from the list that have a recurring amount of zero, but a recurring rate that is not zero
- */
- public void cleanupDuplicatedItems() {
- Iterator<InvoiceItem> iterator = this.iterator();
- while (iterator.hasNext()) {
- InvoiceItem item = iterator.next();
-
- boolean fixedAmountNull = (item.getFixedAmount() == null);
- boolean recurringRateNull = (item.getRecurringRate() == null);
- boolean recurringAmountZero = (item.getRecurringRate() !=null) && (item.getRecurringAmount().compareTo(BigDecimal.ZERO) == 0);
-
- if (fixedAmountNull && (recurringRateNull || recurringAmountZero)) {
- iterator.remove();
- } else if (item.getEndDate() != null && item.getStartDate().compareTo(item.getEndDate()) == 0) {
- iterator.remove();
- } else if (item.getFixedAmount() == null && item.getRecurringAmount() == null) {
- iterator.remove();
- }
- }
- }
-
- public boolean hasInvoiceItemForPhase(final String phaseName) {
- for (final InvoiceItem item : this) {
- if (item.getPhaseName().equals(phaseName)) {
- return true;
- }
- }
-
- return false;
- }
+// public void removeCancellingPairs() {
+// List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+//
+// for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
+// for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
+// InvoiceItem firstItem = this.get(firstItemIndex);
+// InvoiceItem secondItem = this.get(secondItemIndex);
+// if (firstItem.cancels(secondItem)) {
+// itemsToRemove.add(firstItem);
+// itemsToRemove.add(secondItem);
+// }
+// }
+// }
+//
+// this.removeAll(itemsToRemove);
+// }
+
+// /*
+// * removes recurring items from the list that have a recurring amount of zero, but a recurring rate that is not zero
+// */
+// public void cleanupDuplicatedItems() {
+// Iterator<InvoiceItem> iterator = this.iterator();
+// while (iterator.hasNext()) {
+// InvoiceItem item = iterator.next();
+//
+// if (item instanceof RecurringInvoiceItem) {
+// RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+// boolean recurringRateNull = (that.getRate() == null);
+// boolean recurringAmountZero = (that.getAmount() !=null) && (that.getAmount().compareTo(BigDecimal.ZERO) == 0);
+//
+// if (recurringRateNull || recurringAmountZero) {
+// iterator.remove();
+// } else if (that.getEndDate() != null && that.getStartDate().compareTo(that.getEndDate()) == 0) {
+// iterator.remove();
+// }
+// }
+// }
+// }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
index 7482465..554dc62 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
@@ -22,7 +22,7 @@ public class InvoicingConfiguration {
private final static int roundingMethod = BigDecimal.ROUND_HALF_UP;
private final static int numberOfDecimals = 4;
- public static int getRoundingMethod() {
+ public static int getRoundingMode() {
return roundingMethod;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
new file mode 100644
index 0000000..1c75112
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
@@ -0,0 +1,29 @@
+package com.ning.billing.invoice.model;
+
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+
+public class RecurringInvoiceItemData {
+ private final DateTime startDate;
+ private final DateTime endDate;
+ private final BigDecimal numberOfCycles;
+
+ public RecurringInvoiceItemData(DateTime startDate, DateTime endDate, BigDecimal numberOfCycles) {
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.numberOfCycles = numberOfCycles;
+ }
+
+ public DateTime getStartDate() {
+ return startDate;
+ }
+
+ public DateTime getEndDate() {
+ return endDate;
+ }
+
+ public BigDecimal getNumberOfCycles() {
+ return numberOfCycles;
+ }
+}
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
new file mode 100644
index 0000000..5fc1118
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,56 @@
+group FixedPriceInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+ <prefix>id,
+ <prefix>invoice_id,
+ <prefix>subscription_id,
+ <prefix>plan_name,
+ <prefix>phase_name,
+ <prefix>date,
+ <prefix>amount,
+ <prefix>currency
+>>
+
+getById() ::= <<
+ SELECT <fields()>
+ FROM fixed_invoice_items
+ WHERE id = :id;
+>>
+
+getInvoiceItemsByInvoice() ::= <<
+ SELECT <fields()>
+ FROM fixed_invoice_items
+ WHERE invoice_id = :invoiceId;
+>>
+
+getInvoiceItemsByAccount() ::= <<
+ SELECT <fields("rii.")>
+ FROM fixed_invoice_items rii
+ INNER JOIN invoices i ON i.id = rii.invoice_id
+ WHERE i.account_id = :accountId;
+>>
+
+getInvoiceItemsBySubscription() ::= <<
+ SELECT <fields()>
+ FROM fixed_invoice_items
+ WHERE subscription_id = :subscriptionId;
+>>
+
+create() ::= <<
+ INSERT INTO fixed_invoice_items(<fields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+ :date, :amount, :currency);
+>>
+
+update() ::= <<
+ UPDATE fixed_invoice_items
+ SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+ date = :date, amount = :amount, currency = :currency
+ WHERE id = :id;
+>>
+
+test() ::= <<
+ SELECT 1
+ FROM fixed_invoice_items;
+>>
+;
\ No newline at end of file
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 8a6fe38..a7aa135 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
@@ -31,8 +31,8 @@ getInvoicesByAccountAfterDate() ::= <<
getInvoicesBySubscription() ::= <<
SELECT <invoiceFields("i.")>
FROM invoices i
- LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
- WHERE ii.subscription_id = :subscriptionId
+ LEFT JOIN recurring_invoice_items rii ON i.id = rii.invoice_id
+ WHERE rii.subscription_id = :subscriptionId
GROUP BY <invoiceFields("i.")>;
>>
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 28f3c89..f00e06c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -1,5 +1,6 @@
DROP TABLE IF EXISTS invoice_items;
-CREATE TABLE invoice_items (
+DROP TABLE IF EXISTS recurring_invoice_items;
+CREATE TABLE recurring_invoice_items (
id char(36) NOT NULL,
invoice_id char(36) NOT NULL,
subscription_id char(36) NOT NULL,
@@ -7,13 +8,29 @@ CREATE TABLE invoice_items (
phase_name varchar(50) NOT NULL,
start_date datetime NOT NULL,
end_date datetime NOT NULL,
- recurring_amount numeric(10,4) NULL,
- recurring_rate numeric(10,4) NULL,
- fixed_amount numeric(10,4) NULL,
+ amount numeric(10,4) NULL,
+ rate numeric(10,4) NULL,
currency char(3) NOT NULL,
+ reversed_item_id char(36),
PRIMARY KEY(id)
) ENGINE=innodb;
-CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
+CREATE INDEX recurring_invoice_items_subscription_id ON recurring_invoice_items(subscription_id ASC);
+CREATE INDEX recurring_invoice_items_invoice_id ON recurring_invoice_items(invoice_id ASC);
+
+DROP TABLE IF EXISTS fixed_invoice_items;
+CREATE TABLE fixed_invoice_items (
+ id char(36) NOT NULL,
+ invoice_id char(36) NOT NULL,
+ subscription_id char(36) NOT NULL,
+ plan_name varchar(50) NOT NULL,
+ phase_name varchar(50) NOT NULL,
+ date datetime NOT NULL,
+ amount numeric(10,4) NULL,
+ currency char(3) NOT NULL,
+ PRIMARY KEY(id)
+) ENGINE=innodb;
+CREATE INDEX fixed_invoice_items_subscription_id ON fixed_invoice_items(subscription_id ASC);
+CREATE INDEX fixed_invoice_items_invoice_id ON fixed_invoice_items(invoice_id ASC);
DROP TABLE IF EXISTS invoice_locking;
CREATE TABLE invoice_locking (
@@ -56,7 +73,6 @@ GROUP BY invoice_id;
DROP VIEW IF EXISTS invoice_item_summary;
CREATE VIEW invoice_item_summary AS
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
+ CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS amount_invoiced
+FROM recurring_invoice_items
GROUP BY invoice_id;
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 4779495..6957ba0 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
@@ -35,7 +35,7 @@ import com.ning.billing.util.bus.DefaultBusService;
public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
protected InvoiceDao invoiceDao;
- protected InvoiceItemSqlDao invoiceItemDao;
+ protected RecurringInvoiceItemSqlDao recurringInvoiceItemDao;
protected InvoicePaymentSqlDao invoicePaymentDao;
protected InvoiceModuleWithEmbeddedDb module;
@@ -56,7 +56,7 @@ public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
invoiceDao = injector.getInstance(InvoiceDao.class);
invoiceDao.test();
- invoiceItemDao = module.getInvoiceItemSqlDao();
+ recurringInvoiceItemDao = module.getInvoiceItemSqlDao();
invoicePaymentDao = module.getInvoicePaymentSqlDao();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 2369467..90c1eb8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -29,12 +29,13 @@ import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.model.BillingEventSet;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoicePayment;
import com.ning.billing.invoice.model.InvoiceGenerator;
import com.ning.billing.invoice.model.InvoiceItemList;
@@ -84,7 +85,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
UUID subscriptionId = UUID.randomUUID();
DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
- InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), null, Currency.USD);
+ InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
invoice.addInvoiceItem(invoiceItem);
invoiceDao.create(invoice);
@@ -181,7 +182,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("9.0");
BigDecimal amount = rate.multiply(new BigDecimal("3.0"));
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, null, Currency.USD);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, Currency.USD);
invoice.addInvoiceItem(item);
invoiceDao.create(invoice);
@@ -276,17 +277,17 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
DateTime endDate = startDate.plusMonths(1);
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, null, Currency.USD);
- invoiceItemDao.create(item1);
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, null, Currency.USD);
- invoiceItemDao.create(item2);
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
- DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, null, Currency.USD);
- invoiceItemDao.create(item3);
+ RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, Currency.USD);
+ recurringInvoiceItemDao.create(item3);
- DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, null, Currency.USD);
- invoiceItemDao.create(item4);
+ RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, Currency.USD);
+ recurringInvoiceItemDao.create(item4);
// create invoice 2 (subscriptions 1-3)
DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD);
@@ -297,14 +298,14 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
startDate = endDate;
endDate = startDate.plusMonths(1);
- DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
- invoiceItemDao.create(item5);
+ RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item5);
- DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
- invoiceItemDao.create(item6);
+ RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item6);
- DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, null, Currency.USD);
- invoiceItemDao.create(item7);
+ RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, Currency.USD);
+ recurringInvoiceItemDao.create(item7);
// check that each subscription returns the correct number of invoices
List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1);
@@ -362,11 +363,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
- invoiceItemDao.create(item1);
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
- invoiceItemDao.create(item2);
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
BigDecimal payment1 = new BigDecimal("48.0");
InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
@@ -389,11 +390,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
- invoiceItemDao.create(item1);
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
- invoiceItemDao.create(item2);
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
BigDecimal balance = invoiceDao.getAccountBalance(accountId);
assertEquals(balance.compareTo(rate1.add(rate2)), 0);
@@ -427,11 +428,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate1 = new BigDecimal("17.0");
BigDecimal rate2 = new BigDecimal("42.0");
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, null, Currency.USD);
- invoiceItemDao.create(item1);
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, null, Currency.USD);
- invoiceItemDao.create(item2);
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
DateTime upToDate;
Collection<Invoice> invoices;
@@ -453,8 +454,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate3 = new BigDecimal("21.0");
- DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, null, Currency.USD);
- invoiceItemDao.create(item3);
+ RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, Currency.USD);
+ recurringInvoiceItemDao.create(item3);
upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
@@ -471,7 +472,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
*
*/
@Test
- public void testInvoiceGenerationForImmediateChanges() {
+ public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiException {
InvoiceGenerator generator = new DefaultInvoiceGenerator();
UUID accountId = UUID.randomUUID();
@@ -526,7 +527,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
}
@Test
- public void testInvoiceForFreeTrial() {
+ public void testInvoiceForFreeTrial() throws InvoiceApiException {
DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
MockInternationalPrice recurringPrice = new MockInternationalPrice(price);
MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
@@ -544,12 +545,14 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime targetDate = buildDateTime(2011, 1, 15);
InvoiceGenerator generator = new DefaultInvoiceGenerator();
Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
- assertEquals(invoice.getNumberOfItems(), 1);
+
+ // expect one pro-ration it and one full-period item
+ assertEquals(invoice.getNumberOfItems(), 2);
assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
}
@Test
- public void testInvoiceForEmptyEventSet() {
+ public void testInvoiceForEmptyEventSet() throws InvoiceApiException {
InvoiceGenerator generator = new DefaultInvoiceGenerator();
BillingEventSet events = new BillingEventSet();
Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index b2c2f23..dae0725 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -16,25 +16,20 @@
package com.ning.billing.invoice.dao;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
-import org.apache.commons.io.IOUtils;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
import org.joda.time.DateTime;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
@Test
@@ -45,19 +40,18 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
BigDecimal rate = new BigDecimal("20.00");
- InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, rate, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, Currency.USD);
+ recurringInvoiceItemDao.create(item);
- InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
+ RecurringInvoiceItem thisItem = (RecurringInvoiceItem) recurringInvoiceItemDao.getById(item.getId().toString());
assertNotNull(thisItem);
assertEquals(thisItem.getId(), item.getId());
assertEquals(thisItem.getInvoiceId(), item.getInvoiceId());
assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
assertEquals(thisItem.getStartDate(), item.getStartDate());
assertEquals(thisItem.getEndDate(), item.getEndDate());
- assertEquals(thisItem.getRecurringAmount().compareTo(item.getRecurringAmount()), 0);
- assertEquals(thisItem.getRecurringRate().compareTo(item.getRecurringRate()), 0);
- assertEquals(thisItem.getFixedAmount().compareTo(item.getFixedAmount()), 0);
+ assertEquals(thisItem.getAmount().compareTo(item.getRate()), 0);
+ assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
assertEquals(thisItem.getCurrency(), item.getCurrency());
}
@@ -69,11 +63,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
for (int i = 0; i < 3; i++) {
UUID invoiceId = UUID.randomUUID();
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, null, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, Currency.USD);
+ recurringInvoiceItemDao.create(item);
}
- List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
+ List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
assertEquals(items.size(), 3);
}
@@ -86,11 +80,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
for (int i = 0; i < 5; i++) {
UUID subscriptionId = UUID.randomUUID();
BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, amount, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, Currency.USD);
+ recurringInvoiceItemDao.create(item);
}
- List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+ List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
assertEquals(items.size(), 5);
}
@@ -107,10 +101,10 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("20.00");
UUID subscriptionId = UUID.randomUUID();
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, rate, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, Currency.USD);
+ recurringInvoiceItemDao.create(item);
- List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
+ List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
assertEquals(items.size(), 1);
}
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
index 53eca25..c18e1bf 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -21,13 +21,12 @@ import java.io.IOException;
import com.ning.billing.invoice.api.test.InvoiceTestApi;
import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
-import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.invoice.dao.RecurringInvoiceItemSqlDao;
import org.skife.jdbi.v2.IDBI;
import com.ning.billing.account.glue.AccountModule;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.glue.EntitlementModule;
-import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
import com.ning.billing.util.glue.BusModule;
@@ -50,8 +49,8 @@ public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
helper.stopMysql();
}
- public InvoiceItemSqlDao getInvoiceItemSqlDao() {
- return dbi.onDemand(InvoiceItemSqlDao.class);
+ public RecurringInvoiceItemSqlDao getInvoiceItemSqlDao() {
+ return dbi.onDemand(RecurringInvoiceItemSqlDao.class);
}
public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 11f8d62..f24e173 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -22,7 +22,6 @@ import com.ning.billing.catalog.MockPlan;
import com.ning.billing.catalog.MockPlanPhase;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.InternationalPrice;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
@@ -34,11 +33,12 @@ import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.dao.MockSubscription;
import com.ning.billing.invoice.model.BillingEventSet;
import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.InvoiceGenerator;
import com.ning.billing.invoice.model.InvoiceItemList;
import org.joda.time.DateTime;
@@ -51,14 +51,13 @@ import java.util.UUID;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
@Test(groups = {"fast", "invoicing", "invoiceGenerator"})
public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
private final InvoiceGenerator generator = new DefaultInvoiceGenerator();
@Test
- public void testWithNullEventSetAndNullInvoiceSet() {
+ public void testWithNullEventSetAndNullInvoiceSet() throws InvoiceApiException {
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, new BillingEventSet(), new InvoiceItemList(), new DateTime(), Currency.USD);
@@ -66,7 +65,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testWithEmptyEventSet() {
+ public void testWithEmptyEventSet() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -77,7 +76,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testWithSingleMonthlyEvent() {
+ public void testWithSingleMonthlyEvent() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
@@ -97,13 +96,13 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 1);
+ assertEquals(invoice.getNumberOfItems(), 2);
assertEquals(invoice.getTotalAmount(), TWENTY);
assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
}
@Test
- public void testWithSingleMonthlyEventWithLeadingProRation() {
+ public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
@@ -122,7 +121,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 1);
+ assertEquals(invoice.getNumberOfItems(), 2);
BigDecimal expectedNumberOfBillingCycles;
expectedNumberOfBillingCycles = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -131,7 +130,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
+ public void testTwoMonthlySubscriptionsWithAlignedBillingDates() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Plan plan1 = new MockPlan();
@@ -161,14 +160,13 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
+ public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Plan plan1 = new MockPlan();
BigDecimal rate1 = FIVE;
PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
-
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
events.add(event1);
@@ -184,7 +182,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 2);
+ assertEquals(invoice.getNumberOfItems(), 4);
BigDecimal numberOfCyclesEvent1;
numberOfCyclesEvent1 = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -200,7 +198,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
+ public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Plan plan1 = new MockPlan();
@@ -227,12 +225,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 3);
+ assertEquals(invoice.getNumberOfItems(), 4);
assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
}
@Test
- public void testSingleEventWithExistingInvoice() {
+ public void testSingleEventWithExistingInvoice() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
@@ -258,7 +256,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testMultiplePlansWithUtterChaos() {
+ public void testMultiplePlansWithUtterChaos() throws InvoiceApiException {
// plan 1: change of phase from trial to discount followed by immediate cancellation; (covers phase change, cancel, pro-ration)
// plan 2: single plan that moves from trial to discount to evergreen; BCD = 10 (covers phase change)
// plan 3: change of term from monthly (BCD = 20) to annual (BCD = 31; immediate)
@@ -419,7 +417,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testZeroDollarEvents() {
+ public void testZeroDollarEvents() throws InvoiceApiException {
Plan plan = new MockPlan();
PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
BillingEventSet events = new BillingEventSet();
@@ -433,7 +431,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testEndDateIsCorrect() {
+ public void testEndDateIsCorrect() throws InvoiceApiException {
Plan plan = new MockPlan();
PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
BillingEventSet events = new BillingEventSet();
@@ -442,71 +440,14 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
InvoiceGenerator invoiceGenerator = new DefaultInvoiceGenerator();
Invoice invoice = invoiceGenerator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
- InvoiceItem item = invoice.getInvoiceItems().get(0);
+ RecurringInvoiceItem item = (RecurringInvoiceItem) invoice.getInvoiceItems().get(0);
// end date of the invoice item should be equal to exactly one month later
assertEquals(item.getEndDate().compareTo(targetDate.plusMonths(1)), 0);
}
@Test
- public void testImmediateChange() {
- UUID accountId = UUID.randomUUID();
- Subscription subscription = new MockSubscription();
- Plan plan1 = new MockPlan("plan 1");
- PlanPhase plan1phase1 = new MockPlanPhase(plan1, PhaseType.TRIAL);
- PlanPhase plan1phase2 = new MockPlanPhase(plan1, PhaseType.DISCOUNT);
-
- Plan plan2 = new MockPlan("plan 2");
- PlanPhase plan2phase1 = new MockPlanPhase(plan2, PhaseType.TRIAL);
- PlanPhase plan2phase2 = new MockPlanPhase(plan2, PhaseType.DISCOUNT);
-
- InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
- InternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
- InternationalPrice lessCheapPrice = new MockInternationalPrice(new DefaultPrice(FOUR, Currency.USD));
-
- BillingEventSet events = new BillingEventSet();
-
- BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
- plan1, plan1phase1,
- zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
- BillingModeType.IN_ADVANCE, "Test Event 1",
- SubscriptionTransitionType.CREATE);
- BillingEvent event2 = new DefaultBillingEvent(subscription, new DateTime("2012-04-30T00:02:04.000Z"),
- plan1, plan1phase2,
- null, cheapPrice, BillingPeriod.MONTHLY, 1,
- BillingModeType.IN_ADVANCE, "Test Event 2",
- SubscriptionTransitionType.PHASE);
- events.add(event1);
- events.add(event2);
-
- Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
- assertNotNull(invoice1);
- assertEquals(invoice1.getNumberOfItems(), 1);
-
- BillingEvent event3 = new DefaultBillingEvent(subscription, new DateTime("2012-01-31T00:02:04.000Z"),
- plan2, plan2phase1,
- zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
- BillingModeType.IN_ADVANCE, "Test Event 3",
- SubscriptionTransitionType.CHANGE);
- BillingEvent event4 = new DefaultBillingEvent(subscription, new DateTime("2012-04-30T00:02:04.000Z"),
- plan2, plan2phase2,
- null, lessCheapPrice, BillingPeriod.MONTHLY, 1,
- BillingModeType.IN_ADVANCE, "Test Event 4",
- SubscriptionTransitionType.PHASE);
-
- events.add(event3);
- events.add(event4);
-
- InvoiceItemList items = new InvoiceItemList(invoice1.getInvoiceItems());
- Invoice invoice2 = generator.generateInvoice(accountId, events, items, new DateTime("2012-01-31T00:02:04.000Z"), Currency.USD);
-
- assertNotNull(invoice2);
- assertEquals(invoice2.getNumberOfItems(), 2);
- assertEquals(invoice2.getInvoiceItems().get(0).getPlanName(), plan2.getName());
- }
-
- @Test
- private void testFixedPriceLifeCycle() {
+ public void testFixedPriceLifeCycle() throws InvoiceApiException {
UUID accountId = UUID.randomUUID();
Subscription subscription = new MockSubscription();
Plan plan = new MockPlan("plan 1");
@@ -537,7 +478,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-02-01T00:01:00.000-08:00"), Currency.USD);
assertNotNull(invoice1);
assertEquals(invoice1.getNumberOfItems(), 1);
- assertEquals(invoice1.getInvoiceItems().get(0).getEndDate().compareTo(changeDate), 0);
+
+ Invoice invoice2 = generator.generateInvoice(accountId, events, invoice1.getInvoiceItems(), new DateTime("2012-04-05T00:01:00.000-08:00"), Currency.USD);
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 1);
+ FixedPriceInvoiceItem item = (FixedPriceInvoiceItem) invoice2.getInvoiceItems().get(0);
+ assertEquals(item.getDate().compareTo(changeDate), 0);
}
private MockPlanPhase createMockMonthlyPlanPhase() {
@@ -549,13 +495,6 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
null, BillingPeriod.MONTHLY);
}
- private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
- @Nullable final BigDecimal fixedRate, PhaseType phaseType) {
- return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
- new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
- BillingPeriod.MONTHLY, phaseType);
- }
-
private MockPlanPhase createMockMonthlyPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
null, BillingPeriod.MONTHLY, phaseType);
@@ -578,7 +517,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
private void testInvoiceGeneration(final BillingEventSet events, final InvoiceItemList existingInvoiceItems,
final DateTime targetDate, final int expectedNumberOfItems,
- final BigDecimal expectedAmount) {
+ final BigDecimal expectedAmount) throws InvoiceApiException {
Currency currency = Currency.USD;
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
@@ -588,5 +527,6 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
existingInvoiceItems.addAll(invoice.getInvoiceItems());
assertEquals(invoice.getTotalAmount(), expectedAmount);
}
+
// TODO: Jeff C -- how do we ensure that an annual add-on is properly aligned *at the end* with the base plan?
}
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
index b38d076..bd8a38d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
@@ -39,14 +39,6 @@ public class ValidationProRationTests extends ProRationTestBase {
return new InAdvanceBillingMode();
}
- protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
- return getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
- }
-
- protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
- return getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
- }
-
@Test(expectedExceptions = InvalidDateSequenceException.class)
public void testTargetStartEnd() throws InvalidDateSequenceException {
DateTime startDate = buildDateTime(2011, 1, 30);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index afc9d1c..afe95fd 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -17,6 +17,7 @@
package com.ning.billing.invoice.tests;
+import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.invoice.model.InvoicingConfiguration;
import org.joda.time.DateTime;
@@ -24,7 +25,7 @@ import java.math.BigDecimal;
public abstract class InvoicingTestBase {
protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
- protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+ protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
protected static final BigDecimal ZERO = new BigDecimal("0.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal ONE_HALF = new BigDecimal("0.5").setScale(NUMBER_OF_DECIMALS);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
index 174017b..a24b0c0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
@@ -19,23 +19,25 @@ package com.ning.billing.invoice.tests;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.invoice.model.BillingMode;
import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.model.RecurringInvoiceItemData;
import org.joda.time.DateTime;
import java.math.BigDecimal;
+import java.util.List;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
-public abstract class ProRationTestBase extends InvoicingTestBase{
+public abstract class ProRationTestBase extends InvoicingTestBase {
protected abstract BillingMode getBillingMode();
protected abstract BillingPeriod getBillingPeriod();
protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
try {
BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
+ numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay);
- assertEquals(numberOfBillingCycles, expectedValue);
+ assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0, "Actual: " + numberOfBillingCycles.toString() + "; expected: " + expectedValue.toString());
} catch (InvalidDateSequenceException idse) {
throw idse;
} catch (Exception e) {
@@ -46,13 +48,35 @@ public abstract class ProRationTestBase extends InvoicingTestBase{
protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
try {
BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+ numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay);
- assertEquals(numberOfBillingCycles, expectedValue);
+ assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0);
} catch (InvalidDateSequenceException idse) {
throw idse;
} catch (Exception e) {
fail("Unexpected exception: " + e.getMessage());
}
}
+
+ protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+ List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+
+ BigDecimal numberOfBillingCycles = ZERO;
+ for (RecurringInvoiceItemData item : items) {
+ numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+ }
+
+ return numberOfBillingCycles;
+ }
+
+ protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+ List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, targetDate, billingCycleDay, getBillingPeriod());
+
+ BigDecimal numberOfBillingCycles = ZERO;
+ for (RecurringInvoiceItemData item : items) {
+ numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+ }
+
+ return numberOfBillingCycles;
+ }
}
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index b722567..01bb03f 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -35,7 +35,7 @@ import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.payment.TestHelper;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.bus.Bus.EventBusException;
@@ -67,14 +67,13 @@ public abstract class TestPaymentApi {
final BigDecimal amount = new BigDecimal("10.00");
final UUID subscriptionId = UUID.randomUUID();
- invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
+ invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
subscriptionId,
"test plan", "test phase",
now,
now.plusMonths(1),
amount,
new BigDecimal("1.0"),
- null,
Currency.USD));
List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index 2bae34b..87ff44b 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -33,7 +33,7 @@ import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
public class TestHelper {
protected final AccountDao accountDao;
@@ -66,16 +66,18 @@ public class TestHelper {
Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency);
for (InvoiceItem item : items) {
- invoice.addInvoiceItem(new DefaultInvoiceItem(invoice.getId(),
- item.getSubscriptionId(),
- item.getPlanName(),
- item.getPhaseName(),
- item.getStartDate(),
- item.getEndDate(),
- item.getRecurringAmount(),
- item.getRecurringRate(),
- item.getFixedAmount(),
- item.getCurrency()));
+ if (item instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+ invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ recurringInvoiceItem.getSubscriptionId(),
+ recurringInvoiceItem.getPlanName(),
+ recurringInvoiceItem.getPhaseName(),
+ recurringInvoiceItem.getStartDate(),
+ recurringInvoiceItem.getEndDate(),
+ recurringInvoiceItem.getAmount(),
+ recurringInvoiceItem.getRate(),
+ recurringInvoiceItem.getCurrency()));
+ }
}
invoiceDao.create(invoice);
return invoice;
@@ -85,7 +87,7 @@ public class TestHelper {
final DateTime now = new DateTime(DateTimeZone.UTC);
final UUID subscriptionId = UUID.randomUUID();
final BigDecimal amount = new BigDecimal("10.00");
- final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), null, Currency.USD);
+ final InvoiceItem item = new RecurringInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), Currency.USD);
return createTestInvoice(account, now, Currency.USD, item);
}