killbill-aplcache
Changes
Details
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 54b288a..b5af327 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -60,7 +60,6 @@ import com.ning.billing.util.config.InvoiceConfig;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
-import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
@@ -136,7 +135,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
!events.getSubscriptionIdsWithAutoInvoiceOff()
.contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
- if (item.getInvoiceItemType() == InvoiceItemType.FIXED || item.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ) {
+ if ( item.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ) {
existingItems.add(item);
} else {
tree.addItem(item, allSeenItems);
@@ -152,7 +151,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
existingItems.addAll(recurringExistingItems);
}
-
final LocalDate adjustedTargetDate = adjustTargetDate(existingInvoices, targetDate);
final Invoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), adjustedTargetDate, targetCurrency);
@@ -167,6 +165,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
// We don't want the Fixed items to be repaired -- as they are setup fees that should be paid
removeRemainingFixedItemsFromExisting(existingItems);
+
// Add repair items based on what is left in existing items
addRepairItems(existingItems, proposedItems);
@@ -194,8 +193,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
*/
void addRepairItems(final List<InvoiceItem> existingItems, final List<InvoiceItem> proposedItems) {
for (final InvoiceItem existingItem : existingItems) {
- if (existingItem.getInvoiceItemType() == InvoiceItemType.RECURRING ||
- existingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
+ if (existingItem.getInvoiceItemType() == InvoiceItemType.RECURRING) {
final BigDecimal existingAdjustedPositiveAmount = getAdjustedPositiveAmount(existingItems, existingItem.getId());
final BigDecimal amountNegated = existingItem.getAmount() == null ? null : existingItem.getAmount().subtract(existingAdjustedPositiveAmount).negate();
if (amountNegated != null && amountNegated.compareTo(BigDecimal.ZERO) < 0) {
@@ -308,21 +306,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
Objects.firstNonNull(repairedInvoiceItem.getRate(), BigDecimal.ZERO).compareTo(Objects.firstNonNull(invoiceItem.getRate(), BigDecimal.ZERO)) == 0;
}
- /**
- * Check whether the repair invoice item overlaps the proposed item
- *
- * @param repairInvoiceItem the repair invoice item associated to the repaired item for which we pass the subscriptionId
- * @param repairedSubscriptionId the subscriptionId for which this repair points to
- * @param invoiceItem an invoice item to compare to
- * @return
- */
- boolean isRepareeIncludedInRepair(final InvoiceItem repairInvoiceItem, final UUID repairedSubscriptionId, final InvoiceItem invoiceItem) {
- return invoiceItem.getSubscriptionId().equals(repairedSubscriptionId) &&
- repairInvoiceItem.getStartDate().compareTo(invoiceItem.getStartDate()) <= 0 &&
- (invoiceItem.getEndDate() != null &&
- repairInvoiceItem.getEndDate().compareTo(invoiceItem.getEndDate()) >= 0);
- }
-
// We check to see if there are any adjustments that point to the item we are trying to repair
// If we did any CREDIT_ADJ or REFUND_ADJ, then we unfortunately we can't know what is the intent
// was as it applies to the full Invoice, so we ignore it. That might result in an extra positive CBA
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
index b7f3acc..b219108 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
@@ -18,13 +18,10 @@ package com.ning.billing.invoice.tree;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import javax.annotation.Nullable;
-
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceItemType;
@@ -42,7 +39,10 @@ public class AccountItemTree {
}
public void addItem(final InvoiceItem item, final List<InvoiceItem> allItems) {
- if (item.getInvoiceItemType() != InvoiceItemType.RECURRING && item.getInvoiceItemType() != InvoiceItemType.REPAIR_ADJ) {
+ if (item.getInvoiceItemType() != InvoiceItemType.RECURRING &&
+ item.getInvoiceItemType() != InvoiceItemType.REPAIR_ADJ &&
+ item.getInvoiceItemType() != InvoiceItemType.FIXED &&
+ item.getInvoiceItemType() != InvoiceItemType.ITEM_ADJ) {
return;
}
final UUID subscriptionId = getSubscriptionId(item, allItems);
@@ -60,11 +60,32 @@ public class AccountItemTree {
}
}
+ public void mergeWithProposedItems(final List<InvoiceItem> proposedItems) {
+
+ for (SubscriptionItemTree tree : subscriptionItemTree.values()) {
+ tree.flatten(true);
+ }
+
+ for (InvoiceItem item : proposedItems) {
+ final UUID subscriptionId = getSubscriptionId(item, null);
+ SubscriptionItemTree tree = subscriptionItemTree.get(subscriptionId);
+ if (tree == null) {
+ tree = new SubscriptionItemTree(subscriptionId);
+ subscriptionItemTree.put(subscriptionId, tree);
+ }
+ tree.mergeProposedItem(item);
+ }
+
+ for (SubscriptionItemTree tree : subscriptionItemTree.values()) {
+ tree.buildForMerge();
+ }
+ }
+
public List<InvoiceItem> getCurrentExistingItemsView() {
final List<InvoiceItem> result = new ArrayList<InvoiceItem>();
for (SubscriptionItemTree tree : subscriptionItemTree.values()) {
- final List<InvoiceItem> simplifiedView = tree.getSimplifiedView();
+ final List<InvoiceItem> simplifiedView = tree.getView();
if (simplifiedView.size() > 0) {
result.addAll(simplifiedView);
}
@@ -73,7 +94,8 @@ public class AccountItemTree {
}
private UUID getSubscriptionId(final InvoiceItem item, final List<InvoiceItem> allItems) {
- if (item.getInvoiceItemType() == InvoiceItemType.RECURRING) {
+ if (item.getInvoiceItemType() == InvoiceItemType.RECURRING ||
+ item.getInvoiceItemType() == InvoiceItemType.FIXED) {
return item.getSubscriptionId();
} else {
final InvoiceItem linkedItem = Iterables.tryFind(allItems, new Predicate<InvoiceItem>() {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java b/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java
new file mode 100644
index 0000000..4d2801d
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2010-2014 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.tree;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.Days;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.generator.InvoiceDateUtils;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+
+public class Item {
+
+ private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
+ private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+
+ private final UUID id;
+ private final UUID accountId;
+ private final UUID bundleId;
+ private final UUID subscriptionId;
+ private final UUID invoiceId;
+ private final String planName;
+ private final String phaseName;
+ private final LocalDate startDate;
+ private final LocalDate endDate;
+ private final BigDecimal amount;
+ private final BigDecimal rate;
+ private final Currency currency;
+ private final DateTime createdDate;
+ private final UUID linkedId;
+
+ private BigDecimal currentRepairedAmount;
+ private BigDecimal adjustedAmount;
+
+ private final ItemAction action;
+
+ public enum ItemAction {
+ ADD,
+ CANCEL
+ }
+
+ public Item(final Item item, final ItemAction action) {
+ this.id = item.id;
+ this.accountId = item.accountId;
+ this.bundleId = item.bundleId;
+ this.subscriptionId = item.subscriptionId;
+ this.invoiceId = item.invoiceId;
+ this.planName = item.planName;
+ this.phaseName = item.phaseName;
+ this.startDate = item.startDate;
+ this.endDate = item.endDate;
+ this.amount = item.amount;
+ this.rate = item.rate;
+ this.currency = item.currency;
+ this.linkedId = item.linkedId;
+ this.createdDate = item.createdDate;
+ this.currentRepairedAmount = item.currentRepairedAmount;
+ this.adjustedAmount = item.adjustedAmount;
+
+ this.action = action;
+ }
+
+ public Item(final InvoiceItem item, final ItemAction action) {
+ this.id = item.getId();
+ this.accountId = item.getAccountId();
+ this.bundleId = item.getBundleId();
+ this.subscriptionId = item.getSubscriptionId();
+ this.invoiceId = item.getInvoiceId();
+ this.planName = item.getPlanName();
+ this.phaseName = item.getPhaseName();
+ this.startDate = item.getStartDate();
+ this.endDate = item.getEndDate();
+ this.amount = item.getAmount().abs();
+ this.rate = item.getRate();
+ this.currency = item.getCurrency();
+ this.linkedId = item.getLinkedItemId();
+ this.createdDate = item.getCreatedDate();
+ this.action = action;
+
+ this.currentRepairedAmount = BigDecimal.ZERO;
+ this.adjustedAmount = BigDecimal.ZERO;
+ }
+
+ public InvoiceItem toInvoiceItem() {
+ return toProratedInvoiceItem(startDate, endDate);
+ }
+
+ public InvoiceItem toProratedInvoiceItem(final LocalDate newStartDate, final LocalDate newEndDate) {
+
+ int nbTotalDays = Days.daysBetween(startDate, endDate).getDays();
+ final boolean prorated = !(newStartDate.compareTo(startDate) == 0 && newEndDate.compareTo(endDate) == 0);
+
+ final BigDecimal positiveAmount = prorated ?
+ InvoiceDateUtils.calculateProrationBetweenDates(newStartDate, newEndDate, nbTotalDays)
+ .multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE) :
+ amount;
+
+ if (action == ItemAction.ADD) {
+ return new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, newStartDate, newEndDate, positiveAmount, rate, currency);
+ } else {
+ final BigDecimal maxAvailableAmountAfterAdj = amount.subtract(adjustedAmount);
+ final BigDecimal maxAvailableAmountForRepair = maxAvailableAmountAfterAdj.subtract(currentRepairedAmount);
+ final BigDecimal positiveAmountForRepair = positiveAmount.compareTo(maxAvailableAmountForRepair) <= 0 ? positiveAmount : maxAvailableAmountForRepair;
+ return new RepairAdjInvoiceItem(invoiceId, accountId, newStartDate, newEndDate, positiveAmountForRepair.negate(), currency, linkedId);
+ }
+ }
+
+ public void incrementAdjustedAmount(final BigDecimal increment) {
+ Preconditions.checkState(increment.compareTo(BigDecimal.ZERO) > 0);
+ adjustedAmount = adjustedAmount.add(increment);
+ }
+
+ public void incrementCurrentRepairedAmount(final BigDecimal increment) {
+ Preconditions.checkState(increment.compareTo(BigDecimal.ZERO) > 0);
+ currentRepairedAmount = currentRepairedAmount.add(increment);
+ }
+
+ public ItemAction getAction() {
+ return action;
+ }
+
+ public UUID getLinkedId() {
+ return linkedId;
+ }
+
+ public LocalDate getEndDate() {
+ return endDate;
+ }
+
+ public LocalDate getStartDate() {
+ return startDate;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ public boolean isSameKind(final Item other) {
+
+ final InvoiceItem otherItem = other.toInvoiceItem();
+
+ return !id.equals(otherItem.getId()) &&
+ // Finally, for the tricky part... In case of complete repairs, the new invoiceItem will always meet all of the
+ // following conditions: same type, subscription, start date. Depending on the catalog configuration, the end
+ // date check could also match (e.g. repair from annual to monthly). For that scenario, we need to default
+ // to catalog checks (the rate check is a lame check for versioned catalogs).
+
+ Objects.firstNonNull(planName, "").equals(Objects.firstNonNull(otherItem.getPlanName(), "")) &&
+ Objects.firstNonNull(phaseName, "").equals(Objects.firstNonNull(otherItem.getPhaseName(), "")) &&
+ Objects.firstNonNull(rate, BigDecimal.ZERO).compareTo(Objects.firstNonNull(otherItem.getRate(), BigDecimal.ZERO)) == 0;
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
index ba5e0d2..dc258f5 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
@@ -27,114 +27,128 @@ import java.util.ListIterator;
import java.util.Set;
import java.util.UUID;
-import org.joda.time.Days;
import org.joda.time.LocalDate;
import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.InvoiceItemType;
-import com.ning.billing.invoice.generator.InvoiceDateUtils;
-import com.ning.billing.invoice.model.InvoicingConfiguration;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.tree.Item.ItemAction;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
public class ItemsInterval {
- private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
- private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+ private final NodeInterval interval;
+ private LinkedList<Item> items;
-
- private LinkedList<InvoiceItem> items;
-
- public ItemsInterval() {
- this(null);
+ public ItemsInterval(final NodeInterval interval) {
+ this(interval, null);
}
- public ItemsInterval(final InvoiceItem initialItem) {
+ public ItemsInterval(final NodeInterval interval, final Item initialItem) {
+ this.interval = interval;
this.items = Lists.newLinkedList();
if (initialItem != null) {
items.add(initialItem);
}
}
- public List<InvoiceItem> getItems() {
+ public boolean containsItem(final UUID targetId) {
+ return Iterables.tryFind(items, new Predicate<Item>() {
+ @Override
+ public boolean apply(final Item input) {
+ return input.getId().equals(targetId);
+ }
+ }).orNull() != null;
+ }
+
+ public void setAdjustment(final BigDecimal amount, final UUID targetId) {
+ final Item item = Iterables.tryFind(items, new Predicate<Item>() {
+ @Override
+ public boolean apply(final Item input) {
+ return input.getId().equals(targetId);
+ }
+ }).get();
+ item.incrementAdjustedAmount(amount);
+ }
+
+ public List<Item> getItems() {
return items;
}
- public void buildForNonRepairedItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> output) {
- final InvoiceItem item = createRecuringItem(startDate, endDate);
+ public void buildForMissingInterval(final LocalDate startDate, final LocalDate endDate, final List<Item> output, final boolean addRepair) {
+ final Item item = createNewItem(startDate, endDate, addRepair);
if (item != null) {
output.add(item);
}
}
-
- public void buildFromItems(final List<InvoiceItem> output) {
-
-
+ public void buildFromItems(final List<Item> output, final boolean addRepair) {
final Set<UUID> repairedIds = new HashSet<UUID>();
- ListIterator<InvoiceItem> it = items.listIterator(items.size());
+ final ListIterator<Item> it = items.listIterator(items.size());
+
while (it.hasPrevious()) {
- final InvoiceItem cur = it.previous();
- switch (cur.getInvoiceItemType()) {
- case FIXED:
- case RECURRING:
- // The only time we could see that true is a case of full repair, when the repair
- // points to an item that will end up in the same ItemsInterval
+ final Item cur = it.previous();
+ switch (cur.getAction()) {
+ case ADD:
if (!repairedIds.contains(cur.getId())) {
output.add(cur);
}
break;
-
- case REPAIR_ADJ:
- repairedIds.add(cur.getLinkedItemId());
- break;
-
- case ITEM_ADJ:
- // If item has been adjusted, we assume the item should not be re-invoiced so we leave it in the list.
+ case CANCEL:
+ if (cur.getLinkedId() != null) {
+ repairedIds.add(cur.getLinkedId());
+ }
+ if (addRepair) {
+ output.add(cur);
+ }
break;
-
- // Ignored
- case EXTERNAL_CHARGE:
- case CBA_ADJ:
- case CREDIT_ADJ:
- case REFUND_ADJ:
- default:
}
}
}
- public void insertSortedItem(final InvoiceItem item) {
+ // Just ensure that ADD items precedes CANCEL items
+ public void insertSortedItem(final Item item) {
items.add(item);
- Collections.sort(items, new Comparator<InvoiceItem>() {
+ Collections.sort(items, new Comparator<Item>() {
@Override
- public int compare(final InvoiceItem o1, final InvoiceItem o2) {
-
- final int type1 = o1.getInvoiceItemType().ordinal();
- final int type2 = o2.getInvoiceItemType().ordinal();
- return (type1 < type2) ? -1 : ((type1 == type2) ? 0 : 1);
+ public int compare(final Item o1, final Item o2) {
+ if (o1.getAction() == ItemAction.ADD && o2.getAction() == ItemAction.CANCEL) {
+ return -1;
+ } else if (o1.getAction() == ItemAction.CANCEL && o2.getAction() == ItemAction.ADD) {
+ return 1;
+ } else {
+ return 0;
+ }
}
});
}
- private InvoiceItem createRecuringItem(LocalDate startDate, LocalDate endDate) {
+ public void cancelItems(final Item item) {
+ Preconditions.checkState(item.getAction() == ItemAction.ADD);
+ Preconditions.checkState(items.size() == 1);
+ Preconditions.checkState(items.get(0).getAction() == ItemAction.CANCEL);
+ items.clear();
+ }
+
+ private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean addRepair) {
- final List<InvoiceItem> itemToConsider = new LinkedList<InvoiceItem>();
- buildFromItems(itemToConsider);
+ final List<Item> itemToConsider = new LinkedList<Item>();
+ buildFromItems(itemToConsider, addRepair);
- Iterator<InvoiceItem> it = itemToConsider.iterator();
+ Iterator<Item> it = itemToConsider.iterator();
while (it.hasNext()) {
- final InvoiceItem cur = it.next();
- if (cur.getInvoiceItemType() == InvoiceItemType.RECURRING &&
- cur.getStartDate().compareTo(startDate) <= 0 &&
- cur.getEndDate().compareTo(endDate) >= 0) {
- int nbTotalRepairedDays = Days.daysBetween(cur.getStartDate(), cur.getEndDate()).getDays();
- final BigDecimal amount = InvoiceDateUtils.calculateProrationBetweenDates(startDate, endDate, nbTotalRepairedDays).multiply(cur.getRate()).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
- return new RecurringInvoiceItem(cur.getInvoiceId(), cur.getAccountId(), cur.getBundleId(), cur.getSubscriptionId(),
- cur.getPlanName(), cur.getPhaseName(), startDate, endDate, amount, cur.getRate(), cur.getCurrency());
+ final Item cur = it.next();
+ if (cur.getAction() == ItemAction.CANCEL && !addRepair) {
+ continue;
+ }
+ final Item result = new Item(cur.toProratedInvoiceItem(startDate, endDate), cur.getAction());
+ if (cur.getAction() == ItemAction.CANCEL) {
+ cur.incrementCurrentRepairedAmount(result.getAmount());
}
+ return result;
}
return null;
}
-
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
index b714f76..1e0d65a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
@@ -16,12 +16,16 @@
package com.ning.billing.invoice.tree;
+import java.math.BigDecimal;
+import java.util.LinkedList;
import java.util.List;
+import java.util.UUID;
import org.joda.time.LocalDate;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
public class NodeInterval {
@@ -35,23 +39,23 @@ public class NodeInterval {
private NodeInterval rightSibling;
public NodeInterval() {
- this.items = new ItemsInterval();
+ this.items = new ItemsInterval(this);
}
- public NodeInterval(final NodeInterval parent, final InvoiceItem item) {
+ public NodeInterval(final NodeInterval parent, final Item item) {
this.start = item.getStartDate();
this.end = item.getEndDate();
- this.items = new ItemsInterval(item);
+ this.items = new ItemsInterval(this, item);
this.parent = parent;
this.leftChild = null;
this.rightSibling = null;
}
- public void build(final List<InvoiceItem> output) {
+ public void build(final List<Item> output, boolean addRepair) {
// There is no sub-interval, just add our own items.
if (leftChild == null) {
- items.buildFromItems(output);
+ items.buildFromItems(output, addRepair);
return;
}
@@ -59,45 +63,132 @@ public class NodeInterval {
NodeInterval curChild = leftChild;
while (curChild != null) {
if (curChild.getStart().compareTo(curDate) > 0) {
- items.buildForNonRepairedItems(curDate, curChild.getStart(), output);
+ items.buildForMissingInterval(curDate, curChild.getStart(), output, addRepair);
}
- curChild.build(output);
+ curChild.build(output, addRepair);
curDate = curChild.getEnd();
curChild = curChild.getRightSibling();
}
if (curDate.compareTo(end) < 0) {
- items.buildForNonRepairedItems(curDate, end, output);
+ items.buildForMissingInterval(curDate, end, output, addRepair);
}
}
- public boolean isItemContained(final InvoiceItem item) {
+ public boolean isItemContained(final Item item) {
return (item.getStartDate().compareTo(start) >= 0 &&
item.getStartDate().compareTo(end) <= 0 &&
item.getEndDate().compareTo(start) >= 0 &&
item.getEndDate().compareTo(end) <= 0);
}
- public boolean isItemOverlap(final InvoiceItem item) {
+ public boolean isItemOverlap(final Item item) {
return ((item.getStartDate().compareTo(start) < 0 &&
item.getEndDate().compareTo(end) >= 0) ||
(item.getStartDate().compareTo(start) <= 0 &&
item.getEndDate().compareTo(end) > 0));
}
- public void addItem(final InvoiceItem item) {
+ public void addAdjustment(final LocalDate adjustementDate, final BigDecimal amount, final UUID linkedId) {
+ NodeInterval node = findNode(adjustementDate, linkedId);
+ if (node == null) {
+ throw new TreeNodeException("Cannot add adjustement for item = " + linkedId + ", date = " + adjustementDate);
+ }
+ node.setAdjustment(amount.negate(), linkedId);
+ }
+
+ private void setAdjustment(final BigDecimal amount, final UUID linkedId) {
+ items.setAdjustment(amount, linkedId);
+ }
+
+ private NodeInterval findNode(final LocalDate date, final UUID targetItemId) {
+ if (!isRoot()) {
+ throw new TreeNodeException("findNode can only be called from root");
+ }
+ return findNodeRecursively(this, date, targetItemId);
+ }
+
+ private NodeInterval findNodeRecursively(final NodeInterval curNode, final LocalDate date, final UUID targetItemId) {
+ if (date.compareTo(curNode.getStart()) < 0 || date.compareTo(curNode.getEnd()) > 0) {
+ return null;
+ }
+ NodeInterval curChild = curNode.getLeftChild();
+ while (curChild != null) {
+ if (curChild.getStart().compareTo(date) <= 0 && curChild.getEnd().compareTo(date) >= 0) {
+ if (curChild.containsItem(targetItemId)) {
+ return curChild;
+ } else {
+ return findNodeRecursively(curChild, date, targetItemId);
+ }
+ }
+ curChild = curChild.getRightSibling();
+ }
+ return null;
+ }
+
+ public boolean mergeProposedItem(final NodeInterval newNode) {
+ Preconditions.checkState(newNode.getItems().size() == 1, "Expected new node to have only one item");
+ final Item newNodeItem = newNode.getItems().get(0);
+
+ if (!isRoot() && newNodeItem.getStartDate().compareTo(start) == 0 && newNodeItem.getEndDate().compareTo(end) == 0) {
+ items.cancelItems(newNodeItem);
+ return true;
+ }
+ computeRootInterval(newNode);
+
+
+ if (leftChild == null) {
+ leftChild = newNode;
+ return true;
+ }
+
+ NodeInterval prevChild = null;
+ NodeInterval curChild = leftChild;
+ do {
+ Preconditions.checkState(!curChild.isItemOverlap(newNodeItem), "Item " + newNodeItem + " overlaps " + curChild);
+
+ if (curChild.isItemContained(newNodeItem)) {
+ final Item existingNodeItem = curChild.getItems().get(0);
+
+ Preconditions.checkState(curChild.getItems().size() == 1, "Expected existing node to have only one item");
+ if (existingNodeItem.isSameKind(newNodeItem)) {
+ curChild.mergeProposedItem(newNode);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ if (newNodeItem.getStartDate().compareTo(curChild.getStart()) < 0) {
+ newNode.rightSibling = curChild;
+ if (prevChild == null) {
+ leftChild = newNode;
+ } else {
+ prevChild.rightSibling = newNode;
+ }
+ return true;
+ }
+ prevChild = curChild;
+ curChild = curChild.rightSibling;
+ } while (curChild != null);
+
+ prevChild.rightSibling = newNode;
+ return true;
+ }
+
+ public void addNodeInterval(final NodeInterval newNode) {
+ final Item item = newNode.getItems().get(0);
if (!isRoot() && item.getStartDate().compareTo(start) == 0 && item.getEndDate().compareTo(end) == 0) {
items.insertSortedItem(item);
return;
}
-
- final NodeInterval newNode = new NodeInterval(this, item);
computeRootInterval(newNode);
addNode(newNode);
}
+ // STEPH TODO are parents correctly maintained and/or do we need them?
private void addNode(final NodeInterval newNode) {
- final InvoiceItem item = newNode.getItems().get(0);
+ final Item item = newNode.getItems().get(0);
if (leftChild == null) {
leftChild = newNode;
return;
@@ -107,7 +198,7 @@ public class NodeInterval {
NodeInterval curChild = leftChild;
do {
if (curChild.isItemContained(item)) {
- curChild.addItem(item);
+ curChild.addNodeInterval(newNode);
return;
}
@@ -134,13 +225,13 @@ public class NodeInterval {
private void rebalance(final NodeInterval newNode) {
- final InvoiceItem invoiceItem = newNode.getItems().get(0);
+ final Item item = newNode.getItems().get(0);
NodeInterval prevRebalanced = null;
NodeInterval curChild = leftChild;
List<NodeInterval> toBeRebalanced = Lists.newLinkedList();
do {
- if (curChild.isItemOverlap(invoiceItem)) {
+ if (curChild.isItemOverlap(item)) {
toBeRebalanced.add(curChild);
} else {
if (toBeRebalanced.size() > 0) {
@@ -158,7 +249,6 @@ public class NodeInterval {
prevRebalanced.rightSibling = newNode;
}
-
NodeInterval prev = null;
for (NodeInterval cur : toBeRebalanced) {
if (prev == null) {
@@ -202,51 +292,11 @@ public class NodeInterval {
return rightSibling;
}
- public List<InvoiceItem> getItems() {
+ public List<Item> getItems() {
return items.getItems();
}
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof NodeInterval)) {
- return false;
- }
-
- final NodeInterval that = (NodeInterval) o;
-
- if (end != null ? !end.equals(that.end) : that.end != null) {
- return false;
- }
- if (items != null ? !items.equals(that.items) : that.items != null) {
- return false;
- }
- if (leftChild != null ? !leftChild.equals(that.leftChild) : that.leftChild != null) {
- return false;
- }
- if (parent != null ? !parent.equals(that.parent) : that.parent != null) {
- return false;
- }
- if (rightSibling != null ? !rightSibling.equals(that.rightSibling) : that.rightSibling != null) {
- return false;
- }
- if (start != null ? !start.equals(that.start) : that.start != null) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = start != null ? start.hashCode() : 0;
- result = 31 * result + (end != null ? end.hashCode() : 0);
- result = 31 * result + (items != null ? items.hashCode() : 0);
- result = 31 * result + (parent != null ? parent.hashCode() : 0);
- result = 31 * result + (leftChild != null ? leftChild.hashCode() : 0);
- result = 31 * result + (rightSibling != null ? rightSibling.hashCode() : 0);
- return result;
+ public boolean containsItem(final UUID targetId) {
+ return items.containsItem(targetId);
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
index a5686d6..e137c4d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
@@ -20,38 +20,138 @@ import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.tree.Item.ItemAction;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
public class SubscriptionItemTree {
+ private boolean isBuilt;
+
private final UUID subscriptionId;
- private final NodeInterval root;
+ private NodeInterval root;
- private List<InvoiceItem> fixedOrRecuringItems;
+ private List<Item> items;
+
+ private List<InvoiceItem> existingFixedItems;
+ private List<InvoiceItem> remainingFixedItems;
+ private List<InvoiceItem> pendingItemAdj;
public SubscriptionItemTree(final UUID subscriptionId) {
this.subscriptionId = subscriptionId;
this.root = new NodeInterval();
+ this.items = new LinkedList<Item>();
+ this.existingFixedItems = new LinkedList<InvoiceItem>();
+ this.remainingFixedItems = new LinkedList<InvoiceItem>();
+ this.pendingItemAdj = new LinkedList<InvoiceItem>();
+ this.isBuilt = false;
+ }
+
+ public void build() {
+ Preconditions.checkState(!isBuilt);
+ for (InvoiceItem item : pendingItemAdj) {
+ root.addAdjustment(item.getStartDate(), item.getAmount(), item.getLinkedItemId());
+ }
+ pendingItemAdj.clear();
+ root.build(items, false);
+ isBuilt = true;
}
- public void addItem(final InvoiceItem item) {
- if (item.getInvoiceItemType() != InvoiceItemType.RECURRING && item.getInvoiceItemType() != InvoiceItemType.REPAIR_ADJ) {
- return;
+ public void flatten(boolean reverse) {
+ if (!isBuilt) {
+ build();
+ }
+ root = new NodeInterval();
+ for (Item item : items) {
+ final InvoiceItem invoiceItem = item.toInvoiceItem();
+ Preconditions.checkState(item.getAction() == ItemAction.ADD);
+ root.addNodeInterval(new NodeInterval(root, new Item(item, reverse ? ItemAction.CANCEL : ItemAction.ADD)));
}
- root.addItem(item);
+ items.clear();
+ isBuilt = false;
}
+ public void buildForMerge() {
+ Preconditions.checkState(!isBuilt);
+ root.build(items, true);
+ isBuilt = true;
+ }
- public void build() {
- if (fixedOrRecuringItems == null) {
- fixedOrRecuringItems = new LinkedList<InvoiceItem>();
- root.build(fixedOrRecuringItems);
+ public void addItem(final InvoiceItem invoiceItem) {
+
+ Preconditions.checkState(!isBuilt);
+ switch (invoiceItem.getInvoiceItemType()) {
+ case RECURRING:
+ root.addNodeInterval(new NodeInterval(root, new Item(invoiceItem, ItemAction.ADD)));
+ break;
+
+ case REPAIR_ADJ:
+ root.addNodeInterval(new NodeInterval(root, new Item(invoiceItem, ItemAction.CANCEL)));
+ break;
+
+ case FIXED:
+ existingFixedItems.add(invoiceItem);
+ break;
+
+ case ITEM_ADJ:
+ pendingItemAdj.add(invoiceItem);
+ break;
+
+ default:
+ break;
}
}
- public List<InvoiceItem> getSimplifiedView() {
- return fixedOrRecuringItems;
+
+ public void mergeProposedItem(final InvoiceItem invoiceItem) {
+
+ Preconditions.checkState(!isBuilt);
+ switch (invoiceItem.getInvoiceItemType()) {
+ case RECURRING:
+ final boolean result = root.mergeProposedItem(new NodeInterval(root, new Item(invoiceItem, ItemAction.ADD)));
+ if (!result) {
+ items.add(new Item(invoiceItem, ItemAction.ADD));
+ }
+ break;
+
+ case FIXED:
+ final InvoiceItem existingItem = Iterables.tryFind(existingFixedItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.matches(invoiceItem);
+ }
+ }).orNull();
+ if (existingItem == null) {
+ remainingFixedItems.add(invoiceItem);
+ }
+ break;
+
+ default:
+ Preconditions.checkState(false, "Unexpected proposed item " + invoiceItem);
+ }
+
+ }
+
+ public List<InvoiceItem> getView() {
+
+ // STEPH TODO check that nodeInterval don't overlap or throw. => double billing...
+ final List<InvoiceItem> result = new LinkedList<InvoiceItem>();
+ result.addAll(remainingFixedItems);
+ result.addAll(Collections2.transform(items, new Function<Item, InvoiceItem>() {
+ @Override
+ public InvoiceItem apply(final Item input) {
+ return input.toInvoiceItem();
+ }
+ }));
+ return result;
}
public UUID getSubscriptionId() {
@@ -85,4 +185,5 @@ public class SubscriptionItemTree {
result = 31 * result + (root != null ? root.hashCode() : 0);
return result;
}
+
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/TreeNodeException.java b/invoice/src/main/java/com/ning/billing/invoice/tree/TreeNodeException.java
new file mode 100644
index 0000000..4263c63
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/TreeNodeException.java
@@ -0,0 +1,23 @@
+package com.ning.billing.invoice.tree;
+
+public class TreeNodeException extends RuntimeException {
+
+ public TreeNodeException() {
+ }
+
+ public TreeNodeException(final String message) {
+ super(message);
+ }
+
+ public TreeNodeException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public TreeNodeException(final Throwable cause) {
+ super(cause);
+ }
+
+ public TreeNodeException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
index cb02de4..15e099b 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
@@ -280,102 +280,4 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
assertFalse(defaultInvoiceGenerator.isRepareeItemForRepairedItem(annual, annual));
assertFalse(defaultInvoiceGenerator.isRepareeItemForRepairedItem(annual, monthly));
}
-
- /********************************************* isRepareeIncludedInRepair logic ********************************/
-
- // Check for an item whose start and endDate exactly fit in repair item
- @Test(groups = "fast")
- public void testIsRepareeIncludedInRepair1() throws Exception {
- final LocalDate startDate = new LocalDate(2012, 5, 1);
- final LocalDate startRepair = new LocalDate(2012, 8, 1);
- final LocalDate endDate = new LocalDate(2013, 5, 1);
-
- // Repaired item
- final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
- final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, startRepair, endDate, BigDecimal.ONE, currency, repairedItem.getId());
-
- final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepair, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
- assertTrue(defaultInvoiceGenerator.isRepareeIncludedInRepair(repairItem, repairedItem.getSubscriptionId(), invoiceItem));
- }
-
- // Check for an item whose start is greater than repair startDate
- @Test(groups = "fast")
- public void testIsRepareeIncludedInRepair2() throws Exception {
- final LocalDate startDate = new LocalDate(2012, 5, 1);
- final LocalDate startRepair = new LocalDate(2012, 8, 1);
- final LocalDate endDate = new LocalDate(2013, 5, 1);
-
- // Repaired item
- final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
- final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, startRepair, endDate, BigDecimal.ONE, currency, repairedItem.getId());
-
- final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepair.plusDays(1), endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
- assertTrue(defaultInvoiceGenerator.isRepareeIncludedInRepair(repairItem, repairedItem.getSubscriptionId(), invoiceItem));
- }
-
- // Check for an item whose endDate is lower than repair endDate
- @Test(groups = "fast")
- public void testIsRepareeIncludedInRepair3() throws Exception {
- final LocalDate startDate = new LocalDate(2012, 5, 1);
- final LocalDate startRepair = new LocalDate(2012, 8, 1);
- final LocalDate endDate = new LocalDate(2013, 5, 1);
-
- // Repaired item
- final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
- final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, startRepair, endDate, BigDecimal.ONE, currency, repairedItem.getId());
-
- final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepair, endDate.minusDays(1), BigDecimal.TEN, BigDecimal.TEN, currency);
- assertTrue(defaultInvoiceGenerator.isRepareeIncludedInRepair(repairItem, repairedItem.getSubscriptionId(), invoiceItem));
- }
-
- // Check for an item whose endDate is lower than repair endDate
- @Test(groups = "fast")
- public void testIsRepareeIncludedInRepair4() throws Exception {
- final LocalDate startDate = new LocalDate(2012, 5, 1);
- final LocalDate startRepair = new LocalDate(2012, 8, 1);
- final LocalDate endDate = new LocalDate(2013, 5, 1);
-
- // Repaired item
- final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
- final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, startRepair, endDate, BigDecimal.ONE, currency, repairedItem.getId());
-
- final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepair, endDate.minusDays(1), BigDecimal.TEN, BigDecimal.TEN, currency);
- assertTrue(defaultInvoiceGenerator.isRepareeIncludedInRepair(repairItem, repairedItem.getSubscriptionId(), invoiceItem));
- }
-
- // Check for an item whose endDate is greater than repair endDate
- @Test(groups = "fast")
- public void testIsRepareeIncludedInRepair5() throws Exception {
- final LocalDate startDate = new LocalDate(2012, 5, 1);
- final LocalDate startRepair = new LocalDate(2012, 8, 1);
- final LocalDate endDate = new LocalDate(2013, 5, 1);
-
- // Repaired item
- final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
- final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, startRepair, endDate, BigDecimal.ONE, currency, repairedItem.getId());
-
- final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startRepair, endDate.plusDays(1), BigDecimal.TEN, BigDecimal.TEN, currency);
- assertFalse(defaultInvoiceGenerator.isRepareeIncludedInRepair(repairItem, repairedItem.getSubscriptionId(), invoiceItem));
- }
-
- @Test(groups = "fast")
- public void testIsRepareeIncludedInRepairWrongSubscription() throws Exception {
- final LocalDate startDate = new LocalDate(2012, 5, 1);
- final LocalDate startRepair = new LocalDate(2012, 8, 1);
- final LocalDate endDate = new LocalDate(2013, 5, 1);
-
- // Repaired item
- final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
- final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, startRepair, endDate, BigDecimal.ONE, currency, repairedItem.getId());
-
- final UUID otherSubscriptionId = UUID.fromString("a9cbee45-5796-4dc5-be1f-7c020518460d");
- final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, otherSubscriptionId, planName, phaseName, startRepair, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
- assertFalse(defaultInvoiceGenerator.isRepareeIncludedInRepair(repairItem, repairedItem.getSubscriptionId(), invoiceItem));
- }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
index eb13737..6eddcc8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -25,11 +25,13 @@ import org.testng.annotations.Test;
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.invoice.model.ItemAdjInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
import com.google.common.collect.Lists;
+import sun.reflect.annotation.ExceptionProxy;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@@ -74,21 +76,21 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(newItem);
tree.addItem(repair);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
tree = new SubscriptionItemTree(subscriptionId);
tree.addItem(repair);
tree.addItem(newItem);
tree.addItem(initial);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
tree = new SubscriptionItemTree(subscriptionId);
tree.addItem(repair);
tree.addItem(initial);
tree.addItem(newItem);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
}
@Test(groups = "fast")
@@ -133,7 +135,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(newItem2);
tree.addItem(repair2);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
tree = new SubscriptionItemTree(subscriptionId);
tree.addItem(repair2);
@@ -142,7 +144,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(repair1);
tree.addItem(initial);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
tree = new SubscriptionItemTree(subscriptionId);
tree.addItem(repair1);
@@ -151,7 +153,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(repair2);
tree.addItem(newItem2);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
}
@Test(groups = "fast")
@@ -187,35 +189,10 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(block1);
tree.addItem(block2);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
}
- @Test(groups = "fast")
- public void testInvoiceItemAdj() {
-
- final LocalDate startDate = new LocalDate(2014, 1, 1);
- final LocalDate itemAdjDate = new LocalDate(2014, 1, 7);
- final LocalDate endDate = new LocalDate(2014, 2, 1);
-
-
- final BigDecimal rate1 = new BigDecimal("12.00");
- final BigDecimal amount1 = rate1;
-
- final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
- final InvoiceItem adj = new ItemAdjInvoiceItem(initial, itemAdjDate, amount1, currency);
-
- final List<InvoiceItem> expectedResult = Lists.newLinkedList();
- expectedResult.add(initial);
-
- // First test with items in order
- SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
- tree.addItem(initial);
- tree.addItem(adj);
- tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
- }
-
@Test(groups = "fast")
public void testBlockAcrossPeriod() {
@@ -248,7 +225,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(block1);
tree.addItem(block2);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
}
@Test(groups = "fast")
@@ -280,7 +257,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(monthly1);
tree.addItem(monthly2);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
tree = new SubscriptionItemTree(subscriptionId);
@@ -289,7 +266,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(annual);
tree.addItem(monthly2);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
tree = new SubscriptionItemTree(subscriptionId);
tree.addItem(monthly1);
@@ -297,9 +274,392 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.addItem(annual);
tree.addItem(repair);
tree.build();
- verifyResult(tree.getSimplifiedView(), expectedResult);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+ @Test(groups = "fast")
+ public void testMonthlyToAnnualWithLeadingProRation() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endMonthly1 = new LocalDate(2014, 2, 1);
+ final LocalDate endMonthly2 = new LocalDate(2014, 3, 1);
+ final LocalDate switchToAnnualDate = new LocalDate(2014, 2, 23);
+ final LocalDate endDate = new LocalDate(2015, 3, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+ final BigDecimal yearlyRate = new BigDecimal("100.00");
+ final BigDecimal yearlyAmount = yearlyRate;
+
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, switchToAnnualDate, endMonthly2, monthlyAmount.negate(), currency, monthly2.getId());
+ final InvoiceItem leadingAnnualProration = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, switchToAnnualDate, endMonthly2, yearlyAmount, yearlyRate, currency);
+ final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly2, endDate, yearlyAmount, yearlyRate, currency);
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(monthly1);
+ final InvoiceItem monthly2Prorated = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, switchToAnnualDate, new BigDecimal("9.43"), monthlyRate, currency);
+ expectedResult.add(monthly2Prorated);
+ expectedResult.add(leadingAnnualProration);
+ expectedResult.add(annual);
+
+ // First test with items in order
+ SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ tree.addItem(monthly1);
+ tree.addItem(monthly2);
+ tree.addItem(repair);
+ tree.addItem(leadingAnnualProration);
+ tree.addItem(annual);
+ tree.build();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testMonthlyToAnnualWithNoProRation() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endMonthly1 = new LocalDate(2014, 2, 1);
+ final LocalDate endMonthly2 = new LocalDate(2014, 3, 1);
+ final LocalDate switchToAnnualDate = new LocalDate(2014, 2, 23);
+ final LocalDate endDate = new LocalDate(2015, 2, 23);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+ final BigDecimal yearlyRate = new BigDecimal("100.00");
+ final BigDecimal yearlyAmount = yearlyRate;
+
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endMonthly1, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem monthly2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, endMonthly2, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, switchToAnnualDate, endMonthly2, monthlyAmount.negate(), currency, monthly2.getId());
+ final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, switchToAnnualDate, endDate, yearlyAmount, yearlyRate, currency);
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(monthly1);
+ final InvoiceItem monthly2Prorated = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endMonthly1, switchToAnnualDate, new BigDecimal("9.43"), monthlyRate, currency);
+ expectedResult.add(monthly2Prorated);
+ expectedResult.add(annual);
+
+ // First test with items in order
+ SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ tree.addItem(monthly1);
+ tree.addItem(monthly2);
+ tree.addItem(repair);
+ tree.addItem(annual);
+ tree.build();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+
+ @Test(groups = "fast")
+ public void testMergeTwoSimilarItems() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ tree.addItem(monthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeTwoDifferentItems() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount1 = monthlyRate1;
+
+ final BigDecimal monthlyRate2 = new BigDecimal("15.00");
+ final BigDecimal monthlyAmount2 = monthlyRate2;
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+ tree.addItem(monthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount2, monthlyRate2, currency);
+
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, monthlyAmount1.negate(), currency, monthly1.getId());
+ expectedResult.add(proposed1);
+ expectedResult.add(repair);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeWithInitialRepair() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate blockDate = new LocalDate(2014, 1, 25);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount1 = monthlyRate1;
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+ tree.addItem(monthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, blockDate, endDate, monthlyAmount1, monthlyRate1, currency);
+
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, blockDate, new BigDecimal("-9.29"), currency, monthly1.getId());
+ expectedResult.add(repair);
+ expectedResult.add(proposed1);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+ @Test(groups = "fast")
+ public void testMergeWithFinalRepair() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate cancelDate = new LocalDate(2014, 1, 25);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount1 = monthlyRate1;
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+ tree.addItem(monthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
+
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, cancelDate, endDate, new BigDecimal("-2.71"), currency, monthly1.getId());
+ expectedResult.add(proposed1);
+ expectedResult.add(repair);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeWithMiddleRepair() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate blockDate = new LocalDate(2014, 1, 13);
+ final LocalDate unblockDate = new LocalDate(2014, 1, 25);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount1 = monthlyRate1;
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+ tree.addItem(monthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate, monthlyAmount1, monthlyRate1, currency);
+ final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate, endDate, monthlyAmount1, monthlyRate1, currency);
+
+ tree.mergeProposedItem(proposed1);
+ tree.mergeProposedItem(proposed2);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate, unblockDate, new BigDecimal("-4.65"), currency, monthly1.getId());
+ expectedResult.add(proposed1);
+ expectedResult.add(repair);
+ expectedResult.add(proposed2);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeWithTwoMiddleRepair() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate blockDate1 = new LocalDate(2014, 1, 7);
+ final LocalDate unblockDate1 = new LocalDate(2014, 1, 13);
+ final LocalDate blockDate2 = new LocalDate(2014, 1, 17);
+ final LocalDate unblockDate2 = new LocalDate(2014, 1, 25);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ tree.addItem(monthly);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate1, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate1, blockDate2, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate2, endDate, monthlyAmount, monthlyRate, currency);
+
+ tree.mergeProposedItem(proposed1);
+ tree.mergeProposedItem(proposed2);
+ tree.mergeProposedItem(proposed3);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate1, unblockDate1, new BigDecimal("-2.32"), currency, monthly.getId());
+ final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate2, unblockDate2, new BigDecimal("-3.10"), currency, monthly.getId());
+ expectedResult.add(proposed1);
+ expectedResult.add(repair1);
+ expectedResult.add(proposed2);
+ expectedResult.add(repair2);
+ expectedResult.add(proposed3);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+ @Test(groups = "fast")
+ public void testWithExistingFixedItem() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+ final BigDecimal fixedAmount = new BigDecimal("5.00");
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem fixed = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, fixedAmount, currency);
+ tree.addItem(monthly);
+ tree.addItem(fixed);
+ tree.flatten(true);
+
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.mergeProposedItem(fixed);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testWithNewFixedItem() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+ final BigDecimal fixedAmount = new BigDecimal("5.00");
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ tree.addItem(monthly);
+ tree.flatten(true);
+
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ final InvoiceItem fixed = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, fixedAmount, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.mergeProposedItem(fixed);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(fixed);
+ verifyResult(tree.getView(), expectedResult);
}
+
+ @Test(groups = "fast")
+ public void testRepairWithSmallItemAdjustment() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate itemAdjDate = new LocalDate(2014, 1, 2);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final LocalDate cancelDate = new LocalDate(2014, 1, 23);
+
+ final BigDecimal rate1 = new BigDecimal("12.00");
+ final BigDecimal amount1 = rate1;
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+ final InvoiceItem itemAdj = new ItemAdjInvoiceItem(initial, itemAdjDate, new BigDecimal("-2.00"), currency);
+ tree.addItem(initial);
+ tree.addItem(itemAdj);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
+ expectedResult.add(expected1);
+ final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, cancelDate, endDate, new BigDecimal("-3.48"), currency, initial.getId());
+ expectedResult.add(repair1);
+
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
+ @Test(groups = "fast")
+ public void testRepairWithLargeItemAdjustment() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate itemAdjDate = new LocalDate(2014, 1, 2);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final LocalDate cancelDate = new LocalDate(2014, 1, 23);
+
+ final BigDecimal rate1 = new BigDecimal("12.00");
+ final BigDecimal amount1 = rate1;
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+ final InvoiceItem itemAdj = new ItemAdjInvoiceItem(initial, itemAdjDate, new BigDecimal("-10.00"), currency);
+ tree.addItem(initial);
+ tree.addItem(itemAdj);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
+ expectedResult.add(expected1);
+ final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, cancelDate, endDate, new BigDecimal("-2.00"), currency, initial.getId());
+ expectedResult.add(repair1);
+
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+
private void verifyResult(final List<InvoiceItem> result, final List<InvoiceItem> expectedResult) {
assertEquals(result.size(), expectedResult.size());
for (int i = 0; i < expectedResult.size(); i++) {
@@ -307,4 +667,5 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
}
}
+
}