killbill-memoizeit

Fix for #145 The issue is that reparing a block/unblock invoice

12/13/2013 10:55:27 PM

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 d3342c0..4d82d6d 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
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.clock.Clock;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
@@ -49,47 +50,43 @@ import com.ning.billing.invoice.model.InvoicingConfiguration;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItemData;
 import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
-import com.ning.billing.clock.Clock;
-import com.ning.billing.util.config.InvoiceConfig;
 import com.ning.billing.junction.BillingEvent;
 import com.ning.billing.junction.BillingEventSet;
 import com.ning.billing.junction.BillingModeType;
+import com.ning.billing.util.config.InvoiceConfig;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 
-
 /**
  * Terminology for repair scenarii:
- *
+ * <p/>
  * - A 'repaired' item is an item that was generated and that needs to be repaired because the plan changed for that subscription on that period of time
  * - The 'repair' item is the item that cancels the (to be) repaired item; the repair item amount might not match (to be) repaired item because:
- *   * the (to be) repaired item was already adjusted so we will only repair what is left
- *   * in case of partial repair we only repair the part that is not used
+ * * the (to be) repaired item was already adjusted so we will only repair what is left
+ * * in case of partial repair we only repair the part that is not used
  * - The 'reparee' item is only present on disk-- in the existing item list -- in case of full repair; in that case it represents the portion of the item that should still
- *   be invoiced for the plan of the repaired item. In case of partial repair it is merged with the repair item and does not exist except as a virtual item in the proposed list
- *
- *
- *
+ * be invoiced for the plan of the repaired item. In case of partial repair it is merged with the repair item and does not exist except as a virtual item in the proposed list
+ * <p/>
+ * <p/>
+ * <p/>
  * Example. We had a 20 subscription for a given period; we charged that amount and later discovered that only 3/4 of the time period were used after which the subscription was cancelled (immediate canellation)
- *
+ * <p/>
  * Full repair logic:
- *
+ * <p/>
  * Invoice 1:                   Invoice 2:
- *           +20 (repaired)             +5 (reparee)
- *           -20 (repair)
- *
+ * +20 (repaired)             +5 (reparee)
+ * -20 (repair)
+ * <p/>
  * Partial repair logic:
- *
+ * <p/>
  * Invoice 1:                   Invoice 2: (N/A)
- *           +20 (repaired)
- *           -15 (repair)
- *
+ * +20 (repaired)
+ * -15 (repair)
+ * <p/>
  * The current version of the code uses partial repair logic but is able to deal with 'full repair' scenarii.
- *
  */
 
 public class DefaultInvoiceGenerator implements InvoiceGenerator {
@@ -193,15 +190,12 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     /**
      * Add the repair item for the (yet to be) repairedItem. It will merge the candidateRepairItem with reparee item
      *
-     *
-     *
      * @param repairedItem        the item being repaired
      * @param candidateRepairItem the repair item we would have if we were to repair the full period
      * @param proposedItems       the list of proposed items
      */
     void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
 
-
         int nbTotalRepaireeDays = 0;
 
         // totalRepareeItemAmount is negative and represents the portion left after we removed the adjustments for the total period for all the reparees combined
@@ -237,20 +231,21 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         final Iterator<InvoiceItem> it = reparees.iterator();
         while (it.hasNext()) {
             final InvoiceItem nextReparee = it.next();
-            if (prevReparee != null) {
+            if (prevReparee != null || nextReparee.getStartDate().compareTo(repairedItem.getStartDate()) > 0) {
+                final LocalDate repairedStartDate = (prevReparee == null) ? repairedItem.getStartDate() : prevReparee.getEndDate();
+                final LocalDate repairedEndDateDate = nextReparee.getStartDate();
                 // repairItemAmount is an approximation of the exact amount by simply prorating totalRepareeItemAmount in the repair period; we make sure last item is calculated based
                 // on what is left so the sum of all repairs amount is exactly correct
                 final BigDecimal repairItemAmount = (nextReparee.getEndDate().compareTo(candidateRepairItem.getEndDate()) != 0) ?
-                                                    InvoiceDateUtils.calculateProrationBetweenDates(prevReparee.getEndDate(), nextReparee.getStartDate(), nbTotalRepairedDays).multiply(totalRepareeItemAmount) :
+                                                    InvoiceDateUtils.calculateProrationBetweenDates(repairedStartDate, repairedEndDateDate, nbTotalRepairedDays).multiply(totalRepareeItemAmount) :
                                                     totalRepareeItemAmount.subtract(totalRepairItemAmount);
                 totalRepairItemAmount = totalRepairItemAmount.add(repairItemAmount);
-                final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(candidateRepairItem.getInvoiceId(), candidateRepairItem.getAccountId(), prevReparee.getEndDate(), nextReparee.getStartDate(), repairItemAmount, candidateRepairItem.getCurrency(), repairedItem.getId());
+                final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(candidateRepairItem.getInvoiceId(), candidateRepairItem.getAccountId(), repairedStartDate, repairedEndDateDate, repairItemAmount, candidateRepairItem.getCurrency(), repairedItem.getId());
                 repairedItems.add(repairItem);
             }
             prevReparee = nextReparee;
         }
 
-
         // In case we end up with a repair up to the service endDate we need to add this extra item-- this is the 'classic' case with one repairee/repair item
         if (prevReparee.getEndDate().compareTo(candidateRepairItem.getEndDate()) != 0) {
             final BigDecimal repairItemAmount = totalRepareeItemAmount.subtract(totalRepairItemAmount);
@@ -272,7 +267,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
      * @param invoiceItem         any invoice item to compare to
      * @return true if invoiceItem is the reparee for that repaired invoice item
      */
-    @VisibleForTesting
     boolean isRepareeItemForRepairedItem(final InvoiceItem repairedInvoiceItem, final InvoiceItem invoiceItem) {
         return !repairedInvoiceItem.getId().equals(invoiceItem.getId()) &&
                repairedInvoiceItem.getInvoiceItemType().equals(invoiceItem.getInvoiceItemType()) &&
@@ -295,6 +289,20 @@ 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
@@ -376,14 +384,16 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         final List<UUID> itemsToRemove = new ArrayList<UUID>();
         for (final InvoiceItem item : existingItems) {
             if (item.getInvoiceItemType() == InvoiceItemType.REPAIR_ADJ) {
-                itemsToRemove.add(item.getId());
-                itemsToRemove.add(item.getLinkedItemId());
 
-                final InvoiceItem repairedInvoiceItem = getRepairedInvoiceItem(item.getLinkedItemId(), existingItems);
-                // if this is a full repair there is no reparee so nothing to remove; if not reparee needs to be removed from proposed list
-                if (!isFullRepair(repairedInvoiceItem, item, existingItems)) {
-                    removeProposedRepareeForPartialrepair(repairedInvoiceItem, proposedItems);
+                // Assign for terminology purpose
+                final InvoiceItem repairItem = item;
+                itemsToRemove.add(repairItem.getId());
+                itemsToRemove.add(repairItem.getLinkedItemId());
 
+                final InvoiceItem repairedItem = getRepairedInvoiceItem(repairItem.getLinkedItemId(), existingItems);
+                // if this is a full repair there is no reparee so nothing to remove; if not reparees need to be removed from proposed list
+                if (!isFullRepair(repairedItem, repairItem, existingItems)) {
+                    removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
                 }
             }
         }
@@ -418,18 +428,20 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
      * @param repairedItem  the repaired item
      * @param proposedItems the list of existing items
      */
-    protected void removeProposedRepareeForPartialrepair(final InvoiceItem repairedItem, final List<InvoiceItem> proposedItems) {
+    protected void removeProposedRepareesForPartialrepair(final InvoiceItem repairedItem, final InvoiceItem repairItem, final List<InvoiceItem> proposedItems) {
         final Iterator<InvoiceItem> it = proposedItems.iterator();
         while (it.hasNext()) {
             final InvoiceItem cur = it.next();
-            if (isRepareeItemForRepairedItem(repairedItem, cur)) {
+            final UUID repairedSubscriptionId = repairedItem.getSubscriptionId();
+            // We remove the item if we already billed for it, that is:
+            // - 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)) {
                 it.remove();
-                break;
             }
         }
     }
 
-
     private InvoiceItem getRepairedInvoiceItem(final UUID repairedInvoiceItemId, final List<InvoiceItem> existingItems) {
         for (InvoiceItem cur : existingItems) {
             if (cur.getId().equals(repairedInvoiceItemId)) {
@@ -439,7 +451,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         throw new IllegalStateException("Cannot find repaired invoice item " + repairedInvoiceItemId);
     }
 
-
     private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final UUID accountId, final BillingEventSet events,
                                                    final LocalDate targetDate, final Currency currency) throws InvoiceApiException {
         final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java b/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
index f00172a..34c48c7 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
@@ -48,13 +48,6 @@ public class DefaultInvoiceGeneratorWithSwitchRepairLogic extends DefaultInvoice
     }
 
     @Override
-    protected void removeProposedRepareeForPartialrepair(final InvoiceItem repairedItem, final List<InvoiceItem> proposedItems) {
-        if (repairtLogic == REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR) {
-            super.removeProposedRepareeForPartialrepair(repairedItem, proposedItems);
-        }
-    }
-
-    @Override
     void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
         if (repairtLogic == REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR) {
             super.addRepairItem(repairedItem, candidateRepairItem, proposedItems);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 91cc531..fce3a9d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -130,7 +130,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
     public void testWithSingleMonthlyEvent() throws InvoiceApiException, CatalogApiException {
         final BillingEventSet events = new MockBillingEventSet();
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
 
         final Plan plan = new MockPlan();
@@ -150,11 +150,11 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
     }
 
-    private SubscriptionBase createZombieSubscription() {
-        return createZombieSubscription(UUID.randomUUID(), UUID.randomUUID());
+    private SubscriptionBase createSubscription() {
+        return createSubscription(UUID.randomUUID(), UUID.randomUUID());
     }
 
-    private SubscriptionBase createZombieSubscription(final UUID subscriptionId, final UUID bundleId) {
+    private SubscriptionBase createSubscription(final UUID subscriptionId, final UUID bundleId) {
         final SubscriptionBase sub = Mockito.mock(SubscriptionBase.class);
         Mockito.when(sub.getId()).thenReturn(subscriptionId);
         Mockito.when(sub.getBundleId()).thenReturn(bundleId);
@@ -165,7 +165,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testSimpleWithTimeZone() throws InvoiceApiException, CatalogApiException {
         final UUID accountId = UUID.randomUUID();
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final Plan plan = new MockPlan();
         final BigDecimal rate = TEN;
         final PlanPhase phase = createMockMonthlyPlanPhase(rate);
@@ -193,7 +193,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testSimpleWithSingleDiscountEvent() throws Exception {
         final UUID accountId = UUID.randomUUID();
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final Plan plan = new MockPlan("Plan with a single discount phase");
         final PlanPhase phaseEvergreen = createMockMonthlyPlanPhase(EIGHT, PhaseType.DISCOUNT);
         final int bcdLocal = 16;
@@ -216,7 +216,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
     public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException, CatalogApiException {
         final BillingEventSet events = new MockBillingEventSet();
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
 
         final Plan plan = new MockPlan();
@@ -250,7 +250,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final BigDecimal rate2 = TEN;
         final PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
 
         final BillingEvent event1 = createBillingEvent(sub.getId(), sub.getBundleId(), invoiceUtil.buildDate(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
@@ -275,7 +275,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final BigDecimal rate1 = FIVE;
         final PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final BillingEvent event1 = createBillingEvent(sub.getId(), sub.getBundleId(), invoiceUtil.buildDate(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
@@ -312,7 +312,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final BigDecimal rate1 = FIVE;
         final PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final BillingEvent event1 = createBillingEvent(sub.getId(), sub.getBundleId(), invoiceUtil.buildDate(2011, 9, 1), plan1, phase1, 1);
         events.add(event1);
 
@@ -339,7 +339,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
     public void testSingleEventWithExistingInvoice() throws InvoiceApiException, CatalogApiException {
         final BillingEventSet events = new MockBillingEventSet();
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
 
         final Plan plan1 = new MockPlan();
@@ -554,7 +554,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testFixedPriceLifeCycle() throws InvoiceApiException {
         final UUID accountId = UUID.randomUUID();
-        final SubscriptionBase subscription = createZombieSubscription();
+        final SubscriptionBase subscription = createSubscription();
 
         final Plan plan = new MockPlan("plan 1");
         final MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
@@ -765,7 +765,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
     private BillingEvent createBillingEvent(final UUID subscriptionId, final UUID bundleId, final LocalDate startDate,
                                             final Plan plan, final PlanPhase planPhase, final int billCycleDayLocal) throws CatalogApiException {
-        final SubscriptionBase sub = createZombieSubscription(subscriptionId, bundleId);
+        final SubscriptionBase sub = createSubscription(subscriptionId, bundleId);
         final Currency currency = Currency.USD;
 
         return invoiceUtil.createMockBillingEvent(null, sub, startDate.toDateTimeAtStartOfDay(), plan, planPhase,
@@ -794,7 +794,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
         // create a base plan on April 25th
         final UUID accountId = UUID.randomUUID();
-        final SubscriptionBase baseSubscription = createZombieSubscription();
+        final SubscriptionBase baseSubscription = createSubscription();
 
         final Plan basePlan = new MockPlan("base Plan");
         final MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
@@ -816,12 +816,12 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
         // create 2 add ons on April 28th
         final LocalDate april28 = new LocalDate(2012, 4, 28);
-        final SubscriptionBase addOnSubscription1 = createZombieSubscription();
+        final SubscriptionBase addOnSubscription1 = createSubscription();
         final Plan addOn1Plan = new MockPlan("add on 1");
         final PlanPhase addOn1PlanPhaseEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
         events.add(createBillingEvent(addOnSubscription1.getId(), baseSubscription.getBundleId(), april28, addOn1Plan, addOn1PlanPhaseEvergreen, 25));
 
-        final SubscriptionBase addOnSubscription2 = createZombieSubscription();
+        final SubscriptionBase addOnSubscription2 = createSubscription();
         final Plan addOn2Plan = new MockPlan("add on 2");
         final PlanPhase addOn2PlanPhaseEvergreen = new MockPlanPhase(price20, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
         events.add(createBillingEvent(addOnSubscription2.getId(), baseSubscription.getBundleId(), april28, addOn2Plan, addOn2PlanPhaseEvergreen, 25));
@@ -859,7 +859,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
         // create a base plan on April 25th
         final UUID accountId = UUID.randomUUID();
-        final SubscriptionBase originalSubscription = createZombieSubscription();
+        final SubscriptionBase originalSubscription = createSubscription();
 
         final Plan originalPlan = new MockPlan("original plan");
         final MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
@@ -883,7 +883,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
         // change the plan (i.e. repair) on start date
         events.clear();
-        final SubscriptionBase newSubscription = createZombieSubscription();
+        final SubscriptionBase newSubscription = createSubscription();
         final Plan newPlan = new MockPlan("new plan");
         final MockInternationalPrice price5 = new MockInternationalPrice(new DefaultPrice(FIVE, Currency.USD));
         final PlanPhase newPlanEvergreen = new MockPlanPhase(price5, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
@@ -937,7 +937,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final MockBillingEventSet events = new MockBillingEventSet();
         events.setAccountInvoiceOff(true);
 
-        final SubscriptionBase sub = createZombieSubscription();
+        final SubscriptionBase sub = createSubscription();
         final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
 
         final Plan plan = new MockPlan();
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
new file mode 100644
index 0000000..c363267
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2010-2013 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.generator;
+
+import java.math.BigDecimal;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.InvoiceTestSuiteNoDB;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB {
+
+    private final UUID invoiceId = UUID.randomUUID();
+    private final UUID accountId = UUID.randomUUID();
+    private final UUID subscriptionId = UUID.randomUUID();
+    private final UUID bundleId = UUID.randomUUID();
+    private final String planName = "my-plan";
+    private final String phaseName = "my-phase";
+    private final Currency currency = Currency.USD;
+
+    private DefaultInvoiceGenerator defaultInvoiceGenerator;
+
+    @BeforeClass(groups = "fast")
+    protected void beforeClass() throws Exception {
+        super.beforeClass();
+        this.defaultInvoiceGenerator = (DefaultInvoiceGenerator) generator;
+    }
+
+    /*********************************************  addRepairItem logic ********************************/
+
+    //                 repairedItem
+    // |-----------------------------------------------|
+    //
+    //   proposed1     (result ->) repair    proposed2
+    // |-------------|--------------------|------------|
+
+    @Test(groups = "fast")
+    public void testAddRepairedItem1() {
+
+        final LocalDate startDate = new LocalDate(2013, 12, 1);
+        final LocalDate endDate = new LocalDate(2014, 12, 1);
+        final LocalDate endDateProposed1 = new LocalDate(2014, 1, 1);
+        final LocalDate startProposed2 = new LocalDate(2014, 11, 1);
+
+        final BigDecimal rate = new BigDecimal("120.00");
+        final BigDecimal amount = rate;
+
+        final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+
+        final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(repairedItem.getInvoiceId(), repairedItem.getAccountId(), repairedItem.getStartDate(), repairedItem.getEndDate(), repairedItem.getAmount().negate(), repairedItem.getCurrency(), repairedItem.getId());
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDateProposed1, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed1);
+        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startProposed2, endDate, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed2);
+
+        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+
+        assertEquals(proposed.size(), 1);
+        assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
+        assertEquals(proposed.get(0).getEndDate(), startProposed2);
+        assertEquals(proposed.get(0).getLinkedItemId(), repairedItem.getId());
+        assertEquals(proposed.get(0).getAmount(), new BigDecimal("-100.00"));
+        assertEquals(proposed.get(0).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+    }
+
+    //                 repairedItem
+    // |-----------------------------------------------|
+    //
+    //   proposed1       (result ->) repair
+    // |-------------|---------------------------------|
+
+    @Test(groups = "fast")
+    public void testAddRepairedItem2() {
+
+        final LocalDate startDate = new LocalDate(2013, 12, 1);
+        final LocalDate endDate = new LocalDate(2014, 12, 1);
+        final LocalDate endDateProposed1 = new LocalDate(2014, 1, 1);
+
+        final BigDecimal rate = new BigDecimal("120.00");
+        final BigDecimal amount = rate;
+
+        final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+
+        final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(repairedItem.getInvoiceId(), repairedItem.getAccountId(), repairedItem.getStartDate(), repairedItem.getEndDate(), repairedItem.getAmount().negate(), repairedItem.getCurrency(), repairedItem.getId());
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDateProposed1, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed1);
+
+        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+
+        assertEquals(proposed.size(), 1);
+        assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
+        assertEquals(proposed.get(0).getEndDate(), endDate);
+        assertEquals(proposed.get(0).getLinkedItemId(), repairedItem.getId());
+        assertEquals(proposed.get(0).getAmount(), new BigDecimal("-110.00"));
+        assertEquals(proposed.get(0).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+    }
+
+
+    //                 repairedItem
+    // |-----------------------------------------------|
+    //
+    // (result ->) repair                  proposed1
+    // |----------------------------------|-----------|
+
+    @Test(groups = "fast")
+    public void testAddRepairedItem3() {
+
+        final LocalDate startDate = new LocalDate(2013, 12, 1);
+        final LocalDate endDate = new LocalDate(2014, 12, 1);
+        final LocalDate startDateProposed1 = new LocalDate(2014, 1, 1);
+
+        final BigDecimal rate = new BigDecimal("120.00");
+        final BigDecimal amount = rate;
+
+        final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+
+        final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(repairedItem.getInvoiceId(), repairedItem.getAccountId(), repairedItem.getStartDate(), repairedItem.getEndDate(), repairedItem.getAmount().negate(), repairedItem.getCurrency(), repairedItem.getId());
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed1, endDate, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed1);
+
+        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+
+        assertEquals(proposed.size(), 1);
+        assertEquals(proposed.get(0).getStartDate(), startDate);
+        assertEquals(proposed.get(0).getEndDate(), startDateProposed1);
+        assertEquals(proposed.get(0).getLinkedItemId(), repairedItem.getId());
+        assertEquals(proposed.get(0).getAmount(), new BigDecimal("-110.00"));
+        assertEquals(proposed.get(0).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+    }
+
+    //                 repairedItem
+    // |---------------------------------------------------|
+    //
+    //   proposed1  repair1   proposed2   repair2  proposed3
+    // |----------|-------- |-----------|--------|----------|
+
+    @Test(groups = "fast")
+    public void testAddRepairedItem4() {
+
+        final LocalDate startDate = new LocalDate(2013, 12, 1);
+        final LocalDate endDate = new LocalDate(2014, 12, 1);
+        final LocalDate endDateProposed1 = new LocalDate(2014, 1, 1);
+        final LocalDate startDateProposed2 = new LocalDate(2014, 8, 1);
+        final LocalDate endDateProposed2 = new LocalDate(2014, 9, 1);
+        final LocalDate startDateProposed3 = new LocalDate(2014, 11, 1);
+
+        final BigDecimal rate = new BigDecimal("120.00");
+        final BigDecimal amount = rate;
+
+        final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+
+        final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(repairedItem.getInvoiceId(), repairedItem.getAccountId(), repairedItem.getStartDate(), repairedItem.getEndDate(), repairedItem.getAmount().negate(), repairedItem.getCurrency(), repairedItem.getId());
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDateProposed1, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed1);
+        final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed2, endDateProposed2, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed2);
+        final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed3, endDate, BigDecimal.TEN, rate, currency);
+        proposed.add(proposed3);
+
+        defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+
+        assertEquals(proposed.size(), 2);
+        assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
+        assertEquals(proposed.get(0).getEndDate(), startDateProposed2);
+        assertEquals(proposed.get(0).getLinkedItemId(), repairedItem.getId());
+        assertEquals(proposed.get(0).getAmount(), new BigDecimal("-69.894000"));
+        assertEquals(proposed.get(0).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+
+        assertEquals(proposed.get(1).getStartDate(), endDateProposed2);
+        assertEquals(proposed.get(1).getEndDate(), startDateProposed3);
+        assertEquals(proposed.get(1).getLinkedItemId(), repairedItem.getId());
+        assertEquals(proposed.get(1).getAmount(), new BigDecimal("-20.106000"));
+        assertEquals(proposed.get(1).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+    }
+
+
+    /*********************************************  addRepairItems logic ********************************/
+
+    @Test(groups = "fast")
+    public void testAddRepairedItemsItemsRecurringPrice() {
+        final LocalDate startDate = new LocalDate(2013, 12, 13);
+        final LocalDate endDate = new LocalDate(2014, 1, 12);
+        final LocalDate nextEndDate = new LocalDate(2014, 1, 13);
+
+        final BigDecimal rate1 = new BigDecimal("12.00");
+        final BigDecimal amount1 = rate1;
+
+        final BigDecimal rate2 = new BigDecimal("14.85");
+        final BigDecimal amount2 = rate2;
+
+        final UUID firstInvoiceId = UUID.randomUUID();
+        final List<InvoiceItem> existing = new LinkedList<InvoiceItem>();
+        final InvoiceItem item1 = new RecurringInvoiceItem(firstInvoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+        existing.add(item1);
+
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem other = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endDate, nextEndDate, amount2, rate2, currency);
+        proposed.add(other);
+
+        defaultInvoiceGenerator.addRepairItems(existing, proposed);
+        assertEquals(existing.size(), 1);
+        assertEquals(proposed.size(), 2);
+        assertEquals(proposed.get(0), other);
+
+        final InvoiceItem newItem2 = proposed.get(1);
+        assertEquals(newItem2.getInvoiceId(), firstInvoiceId);
+        assertEquals(newItem2.getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+        assertEquals(newItem2.getAmount(), item1.getAmount().negate());
+        assertEquals(newItem2.getLinkedItemId(), item1.getId());
+        assertEquals(newItem2.getStartDate(), startDate);
+        assertEquals(newItem2.getEndDate(), endDate);
+    }
+
+    /*********************************************  isRepareeItemForRepairedItem logic ********************************/
+
+    @Test(groups = "fast")
+    public void testShouldFindRepareeForPartialRepairs() throws Exception {
+        final LocalDate startDate = new LocalDate(2012, 5, 1);
+        final LocalDate endDate = new LocalDate(2012, 6, 1);
+        // Repaired item
+        final InvoiceItem silver = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
+
+        // Reparee item
+        final LocalDate actualEndDateSilver = new LocalDate(2012, 5, 10);
+        final InvoiceItem actualSilver = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, actualEndDateSilver, new BigDecimal("3"), BigDecimal.TEN, currency);
+
+        // New item
+        final InvoiceItem gold = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "new-" + planName, phaseName, actualEndDateSilver, endDate, BigDecimal.TEN, new BigDecimal("15"), currency);
+
+        assertFalse(defaultInvoiceGenerator.isRepareeItemForRepairedItem(silver, silver));
+        assertFalse(defaultInvoiceGenerator.isRepareeItemForRepairedItem(silver, gold));
+        assertTrue(defaultInvoiceGenerator.isRepareeItemForRepairedItem(silver, actualSilver));
+    }
+
+    @Test(groups = "fast")
+    public void testShouldntFindRepareeForFullRepairs() throws Exception {
+        final LocalDate startDate = new LocalDate(2012, 5, 1);
+        final LocalDate endDate = new LocalDate(2013, 5, 1);
+        // Repaired item
+        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
+
+        // There is no reparee - full repair
+
+        // New item
+        final LocalDate endDate2 = new LocalDate(2012, 6, 1);
+        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "new-" + planName, phaseName, startDate, endDate2, BigDecimal.TEN, BigDecimal.TEN, currency);
+
+        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));
+    }
+
+    /***********************************  removeProposedRepareesForPartialrepair logic ********************************/
+
+    //
+    // Test removal of proposed item after a repair, scenario 1:
+    //
+    // 1. Initially bill the full period:
+    //                 repairedItem
+    // |-----------------------------------------------|
+    //
+    // 2. Block subscription -> repair
+    //   reparee1       repaired
+    // |-------------|---------------------------------|
+    //
+    // 3. Unblock later -> needs to bill for the last part:
+    //    * proposed items = {reparee1; reparee2}
+    //
+    //   reparee1                           reparee2
+    // |-------------|--------------------|------------|
+    //
+    // => Code should detect that reparee1 was already accounted for, but not reparee2
+    //    and therefore only reparee2 should remain in the proposed list.
+    //
+    @Test(groups = "fast")
+    public void testRemoveProposedRepareeForPartialRepair1() {
+
+        final LocalDate startDate = new LocalDate(2012, 6, 30);
+        final LocalDate blockDate = new LocalDate(2012, 7, 10);
+        final LocalDate unblockDate = new LocalDate(2012, 7, 23);
+        final LocalDate endDate = new LocalDate(2012, 7, 31);
+
+        final BigDecimal someAmount = new BigDecimal("100.00");
+
+        final List<InvoiceItem> existing = new LinkedList<InvoiceItem>();
+        final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, someAmount, someAmount, currency);
+        existing.add(repairedItem);
+
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem reparee1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate, someAmount, someAmount, currency);
+        proposed.add(reparee1);
+        final InvoiceItem reparee2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate, endDate, someAmount, someAmount, currency);
+        proposed.add(reparee2);
+
+        final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate, endDate, someAmount, currency, repairedItem.getId());
+
+        defaultInvoiceGenerator.removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposed);
+
+        assertEquals(proposed.size(), 1);
+        assertTrue(proposed.get(0).equals(reparee2));
+    }
+
+    //
+    // Test removal of proposed item after a repair, scenario 2:
+    //
+    // 1. Initially bill the full period:
+    //                 repairedItem
+    // |-----------------------------------------------|
+    //
+    // 2. Block and Unblock later (SAME TIME). Only notifies invoice at the unblock time
+    //    * proposed items = {reparee1; reparee2}
+    //
+    //   reparee1          repaired          reparee2
+    // |-------------|--------------------|------------|
+    //
+    // => Code should detect that both reparee1 and reparee2 were already accounted for, so
+    //    nothing should stay in the proposed list.
+    //
+    @Test(groups = "fast")
+    public void testRemoveProposedRepareeForPartialRepair2() {
+
+        final LocalDate startDate = new LocalDate(2012, 6, 30);
+        final LocalDate blockDate = new LocalDate(2012, 7, 10);
+        final LocalDate unblockDate = new LocalDate(2012, 7, 23);
+        final LocalDate endDate = new LocalDate(2012, 7, 31);
+
+        final BigDecimal someAmount = new BigDecimal("100.00");
+
+        final List<InvoiceItem> existing = new LinkedList<InvoiceItem>();
+        final InvoiceItem repairedItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, someAmount, someAmount, currency);
+        existing.add(repairedItem);
+
+        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
+        final InvoiceItem reparee1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, blockDate, someAmount, someAmount, currency);
+        proposed.add(reparee1);
+        final InvoiceItem reparee2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, unblockDate, endDate, someAmount, someAmount, currency);
+        proposed.add(reparee2);
+
+        final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate, unblockDate, someAmount, currency, repairedItem.getId());
+
+        defaultInvoiceGenerator.removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposed);
+
+        assertEquals(proposed.size(), 0);
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java
index 8d12312..5d02e64 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.api.Currency;
@@ -46,6 +47,13 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
     private final String phaseName = "my-phase";
     private final Currency currency = Currency.USD;
 
+    private DefaultInvoiceGenerator defaultInvoiceGenerator;
+
+    @BeforeClass(groups = "fast")
+    protected void beforeClass() throws Exception {
+        super.beforeClass();
+        this.defaultInvoiceGenerator = (DefaultInvoiceGenerator) generator;
+    }
 
     @Test(groups = "fast")
     public void testRemoveCancellingInvoiceItemsFixedPrice() {
@@ -61,7 +69,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         items.add(item1);
         items.add(new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, amount.negate(), currency, item1.getId()));
         items.add(new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endDate, nextEndDate, amount2, rate2, currency));
-        ((DefaultInvoiceGenerator) generator).removeRepairedAndRepairInvoiceItems(items, new LinkedList<InvoiceItem>());
+        defaultInvoiceGenerator.removeRepairedAndRepairInvoiceItems(items, new LinkedList<InvoiceItem>());
         assertEquals(items.size(), 1);
         final InvoiceItem leftItem = items.get(0);
         assertEquals(leftItem.getInvoiceItemType(), InvoiceItemType.RECURRING);
@@ -83,7 +91,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         items.add(item1);
         items.add(new RepairAdjInvoiceItem(invoiceId, accountId, startDate, endDate, amount1.negate(), currency, item1.getId()));
         items.add(new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endDate, nextEndDate, amount2, rate2, currency));
-        ((DefaultInvoiceGenerator) generator).removeRepairedAndRepairInvoiceItems(items, new LinkedList<InvoiceItem>());
+        defaultInvoiceGenerator.removeRepairedAndRepairInvoiceItems(items, new LinkedList<InvoiceItem>());
         assertEquals(items.size(), 1);
         final InvoiceItem leftItem = items.get(0);
         assertEquals(leftItem.getInvoiceItemType(), InvoiceItemType.RECURRING);
@@ -107,7 +115,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         final InvoiceItem other1 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, amount, currency);
         proposed.add(other1);
 
-        ((DefaultInvoiceGenerator) generator).removeMatchingInvoiceItems(proposed, existing);
+        defaultInvoiceGenerator.removeMatchingInvoiceItems(proposed, existing);
         assertEquals(existing.size(), 1);
         assertEquals(proposed.size(), 0);
     }
@@ -129,7 +137,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         proposed.add(other1);
         proposed.add(other2);
 
-        ((DefaultInvoiceGenerator) generator).removeMatchingInvoiceItems(proposed, existing);
+        defaultInvoiceGenerator.removeMatchingInvoiceItems(proposed, existing);
         assertEquals(existing.size(), 0);
         assertEquals(proposed.size(), 1);
     }
@@ -156,7 +164,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         proposed.add(other1);
         proposed.add(other2);
 
-        ((DefaultInvoiceGenerator) generator).removeMatchingInvoiceItems(proposed, existing);
+        defaultInvoiceGenerator.removeMatchingInvoiceItems(proposed, existing);
         assertEquals(existing.size(), 0);
         assertEquals(proposed.size(), 1);
     }
@@ -183,7 +191,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         proposed.add(item1);
         proposed.add(other);
 
-        ((DefaultInvoiceGenerator) generator).removeMatchingInvoiceItems(proposed, existing);
+        defaultInvoiceGenerator.removeMatchingInvoiceItems(proposed, existing);
         assertEquals(existing.size(), 0);
         assertEquals(proposed.size(), 1);
         final InvoiceItem leftItem = proposed.get(0);
@@ -212,7 +220,7 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         proposed.add(item1);
         proposed.add(other);
 
-        ((DefaultInvoiceGenerator) generator).removeMatchingInvoiceItems(proposed, existing);
+        defaultInvoiceGenerator.removeMatchingInvoiceItems(proposed, existing);
         assertEquals(existing.size(), 0);
         assertEquals(proposed.size(), 1);
         final InvoiceItem leftItem = proposed.get(0);
@@ -220,76 +228,5 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         assertEquals(leftItem.getAmount(), amount2);
     }
 
-    // STEPH same as testRemoveCancellingInvoiceItemsFixedPrice: should we have one for FixedPrice?
-    @Test(groups = "fast")
-    public void testAddRepairedItemsItemsRecurringPrice() {
-        final LocalDate startDate = clock.getUTCToday();
-        final LocalDate endDate = startDate.plusDays(30);
-        final LocalDate nextEndDate = startDate.plusMonths(1);
-
-        final BigDecimal rate1 = new BigDecimal("12.00");
-        final BigDecimal amount1 = rate1;
-
-        final BigDecimal rate2 = new BigDecimal("14.85");
-        final BigDecimal amount2 = rate2;
-
-        final UUID firstInvoiceId = UUID.randomUUID();
-        final List<InvoiceItem> existing = new LinkedList<InvoiceItem>();
-        final InvoiceItem item1 = new RecurringInvoiceItem(firstInvoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        existing.add(item1);
-
-        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
-        final InvoiceItem other = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, endDate, nextEndDate, amount2, rate2, currency);
-        proposed.add(other);
-
-        ((DefaultInvoiceGenerator) generator).addRepairItems(existing, proposed);
-        assertEquals(existing.size(), 1);
-        assertEquals(proposed.size(), 2);
-        final InvoiceItem leftItem1 = proposed.get(0);
-        assertEquals(leftItem1.getInvoiceId(), invoiceId);
-        assertEquals(leftItem1.getInvoiceItemType(), InvoiceItemType.RECURRING);
-        assertEquals(leftItem1.getAmount(), amount2);
-
-        final InvoiceItem newItem2 = proposed.get(1);
-        assertEquals(newItem2.getInvoiceId(), firstInvoiceId);
-        assertEquals(newItem2.getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
-        assertEquals(newItem2.getAmount(), item1.getAmount().negate());
-        assertEquals(newItem2.getLinkedItemId(), item1.getId());
-    }
-
-    @Test(groups = "fast")
-    public void testShouldFindRepareeForPartialRepairs() throws Exception {
-        final LocalDate startDate = new LocalDate(2012, 5, 1);
-        final LocalDate endDate = new LocalDate(2012, 6, 1);
-        // Repaired item
-        final InvoiceItem silver = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
-
-        // Reparee item
-        final LocalDate actualEndDateSilver = new LocalDate(2012, 5, 10);
-        final InvoiceItem actualSilver = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, actualEndDateSilver, new BigDecimal("3"), BigDecimal.TEN, currency);
-
-        // New item
-        final InvoiceItem gold = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "new-" + planName, phaseName, actualEndDateSilver, endDate, BigDecimal.TEN, new BigDecimal("15"), currency);
-
-        assertFalse(((DefaultInvoiceGenerator) generator).isRepareeItemForRepairedItem(silver, silver));
-        assertFalse(((DefaultInvoiceGenerator) generator).isRepareeItemForRepairedItem(silver, gold));
-        assertTrue(((DefaultInvoiceGenerator) generator).isRepareeItemForRepairedItem(silver, actualSilver));
-    }
-
-    @Test(groups = "fast")
-    public void testShouldntFindRepareeForFullRepairs() throws Exception {
-        final LocalDate startDate = new LocalDate(2012, 5, 1);
-        final LocalDate endDate = new LocalDate(2013, 5, 1);
-        // Repaired item
-        final InvoiceItem annual = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, BigDecimal.TEN, BigDecimal.TEN, currency);
 
-        // There is no reparee - full repair
-
-        // New item
-        final LocalDate endDate2 = new LocalDate(2012, 6, 1);
-        final InvoiceItem monthly = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "new-" + planName, phaseName, startDate, endDate2, BigDecimal.TEN, BigDecimal.TEN, currency);
-
-        assertFalse(((DefaultInvoiceGenerator) generator).isRepareeItemForRepairedItem(annual, annual));
-        assertFalse(((DefaultInvoiceGenerator) generator).isRepareeItemForRepairedItem(annual, monthly));
-    }
-}
+}
\ No newline at end of file