killbill-memoizeit

Fixes #159. The code now adds in the list of existing items

2/4/2014 12:24:34 AM

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 78d5e9a..7397f07 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
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
@@ -58,6 +59,7 @@ 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;
 
 /**
@@ -382,19 +384,22 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     void removeRepairedAndRepairInvoiceItems(final List<InvoiceItem> existingItems, final List<InvoiceItem> proposedItems) {
 
         final List<UUID> itemsToRemove = new ArrayList<UUID>();
+        List<InvoiceItem> itemsToAdd = Lists.newLinkedList();
+
         for (final InvoiceItem item : existingItems) {
             if (item.getInvoiceItemType() == InvoiceItemType.REPAIR_ADJ) {
 
                 // Assign for terminology purpose
                 final InvoiceItem repairItem = item;
-                itemsToRemove.add(repairItem.getId());
-                itemsToRemove.add(repairItem.getLinkedItemId());
-
                 final InvoiceItem repairedItem = getRepairedInvoiceItem(repairItem.getLinkedItemId(), existingItems);
                 // Always look for reparees; if this is a full repair there may not be any reparee to remove, but
                 // if this is a partial repair with an additional invoice item adjustment, this is seen as a full repair
                 // and yet there is a reparee to remove
-                removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
+
+                final List<InvoiceItem> removedReparees = removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
+                itemsToAdd.addAll((computeNonRepairedItems(repairedItem, repairItem, removedReparees)));
+                itemsToRemove.add(repairItem.getId());
+                itemsToRemove.add(repairItem.getLinkedItemId());
             }
         }
         final Iterator<InvoiceItem> iterator = existingItems.iterator();
@@ -404,23 +409,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 iterator.remove();
             }
         }
+        existingItems.addAll(itemsToAdd);
     }
 
-    /**
-     * A full repair is one when the whole period was repaired. we reconstruct all the adjustment + repair pointing to the repaired item
-     * and if the amount matches this is a full repair.
-     *
-     * @param repairedItem  the repaired item
-     * @param repairItem    the repair item
-     * @param existingItems the list of existing items
-     * @return true if this is a full repair.
-     */
-    private boolean isFullRepair(final InvoiceItem repairedItem, final InvoiceItem repairItem, final List<InvoiceItem> existingItems) {
-
-        final BigDecimal adjustedPositiveAmount = getAdjustedPositiveAmount(existingItems, repairedItem.getId());
-        final BigDecimal repairAndAdjustedPositiveAmount = repairItem.getAmount().negate().add(adjustedPositiveAmount);
-        return (repairedItem.getAmount().compareTo(repairAndAdjustedPositiveAmount) == 0);
-    }
 
     /**
      * Removes the reparee from proposed list of items if it exists.
@@ -428,7 +419,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
      * @param repairedItem  the repaired item
      * @param proposedItems the list of existing items
      */
-    protected void removeProposedRepareesForPartialrepair(final InvoiceItem repairedItem, final InvoiceItem repairItem, final List<InvoiceItem> proposedItems) {
+    protected List<InvoiceItem> removeProposedRepareesForPartialrepair(final InvoiceItem repairedItem, final InvoiceItem repairItem, final List<InvoiceItem> proposedItems) {
+
+        List<InvoiceItem> removedReparees = Collections.emptyList();
         final Iterator<InvoiceItem> it = proposedItems.iterator();
         while (it.hasNext()) {
             final InvoiceItem cur = it.next();
@@ -437,9 +430,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             // - First we check if the current item is a reparee for that repaired
             // - Second we check whether that reparee is outside of the repair period and therefore has already been accounted for. If not we keep it.
             if (isRepareeItemForRepairedItem(repairedItem, cur) && !isRepareeIncludedInRepair(repairItem, repairedSubscriptionId, cur)) {
+                if (removedReparees.size() == 0) {
+                    removedReparees = Lists.newLinkedList();
+                }
+                removedReparees.add(cur);
                 it.remove();
             }
         }
+        return removedReparees;
     }
 
     private InvoiceItem getRepairedInvoiceItem(final UUID repairedInvoiceItemId, final List<InvoiceItem> existingItems) {
@@ -544,6 +542,71 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         return items;
     }
 
+    /**
+     *
+     * It compares the full period of the repairedItem with the list of repairees and repair
+     *
+     * @param repairedItem     the repair item
+     * @param repairItem       (one of) the repair item pointing to the repairedItem
+     * @param removedReparees  the reparees from propsoed list that were found matching that repairedItem
+     * @return
+     */
+    List<InvoiceItem> computeNonRepairedItems(final InvoiceItem repairedItem, final InvoiceItem repairItem, final List<InvoiceItem> removedReparees) {
+
+        final List<InvoiceItem> result = new LinkedList<InvoiceItem>();
+        if (removedReparees.size() == 0 || repairedItem.getInvoiceItemType() != InvoiceItemType.RECURRING) {
+            return result;
+        }
+
+        final List<InvoiceItem> repairAndReparees = new ArrayList<InvoiceItem>(removedReparees);
+        repairAndReparees.add(repairItem);
+
+        Collections.sort(repairAndReparees, new Comparator<InvoiceItem>() {
+            @Override
+            public int compare(final InvoiceItem o1, final InvoiceItem o2) {
+                return o1.getStartDate().compareTo(o2.getStartDate());
+            }
+        });
+
+        int nbTotalRepairedDays = Days.daysBetween(repairedItem.getStartDate(), repairedItem.getEndDate()).getDays();
+
+        LocalDate prevEnd = null;
+        final LocalDate startDate = repairedItem.getStartDate();
+        for (InvoiceItem cur : repairAndReparees) {
+            if (prevEnd == null) {
+                if (cur.getStartDate().compareTo(startDate) > 0) {
+                    result.add(createRecurringInvoiceItemForRepair(repairedItem.getStartDate(), cur.getStartDate(), repairedItem, nbTotalRepairedDays));
+                }
+            } else {
+                if (prevEnd.compareTo(cur.getStartDate()) < 0) {
+                    result.add(createRecurringInvoiceItemForRepair(prevEnd, cur.getStartDate(), repairedItem, nbTotalRepairedDays));
+                }
+            }
+            prevEnd = cur.getEndDate();
+        }
+
+        if (prevEnd.compareTo(repairedItem.getEndDate()) < 0) {
+            result.add(createRecurringInvoiceItemForRepair(prevEnd, repairedItem.getEndDate(), repairedItem, nbTotalRepairedDays));
+        }
+        return result;
+    }
+
+    private InvoiceItem createRecurringInvoiceItemForRepair(final LocalDate startDate, final LocalDate endDate, final InvoiceItem repairedItem, final int nbTotalRepairedDays) {
+        final BigDecimal amount = InvoiceDateUtils.calculateProrationBetweenDates(startDate, endDate, nbTotalRepairedDays).multiply(repairedItem.getRate()).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
+        return new RecurringInvoiceItem(repairedItem.getInvoiceId(),
+                                        repairedItem.getAccountId(),
+                                        repairedItem.getBundleId(),
+                                        repairedItem.getSubscriptionId(),
+                                        repairedItem.getPlanName(),
+                                        repairedItem.getPhaseName(),
+                                        startDate,
+                                        endDate,
+                                        amount,
+                                        repairedItem.getRate(),
+                                        repairedItem.getCurrency());
+
+    }
+
     private BillingMode instantiateBillingMode(final BillingModeType billingMode) {
         switch (billingMode) {
             case IN_ADVANCE: