killbill-uncached

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 19c14f2..b53e9cc 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
@@ -118,11 +118,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         // Add repair items based on what is left in existing items
         addRepairItems(existingItems, proposedItems);
 
-        // Go through each invoice and if balance is negative, generate CBA of opposite amount -- which could happen if there was some repair
-        // TODO Should this be merged with existing CBA logic in the dao layer ?
-        generateCBAForExistingInvoices(accountId, existingInvoices, proposedItems, targetCurrency);
-
-        // Finally add thos new items on the new invoice
+        // Finally add this new items on the new invoice
         invoice.addInvoiceItems(proposedItems);
 
         return proposedItems.size() != 0 ?  invoice : null;
@@ -171,13 +167,61 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 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) {
-                    final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(existingItem.getInvoiceId(), existingItem.getAccountId(), existingItem.getStartDate(), existingItem.getEndDate(), amountNegated, existingItem.getCurrency(), existingItem.getId());
-                    proposedItems.add(repairItem);
+                    final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(existingItem.getInvoiceId(), existingItem.getAccountId(), existingItem.getStartDate(), existingItem.getEndDate(), amountNegated, existingItem.getCurrency(), existingItem.getId());
+                    addRepairItem(existingItem, candidateRepairItem, proposedItems);
                 }
             }
         }
     }
 
+    void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
+
+        final boolean withPartialRepair = false; //(System.getProperty("InvoiceWithPartialRepair") != null);
+
+        if (!withPartialRepair) {
+            proposedItems.add(candidateRepairItem);
+            return;
+        }
+
+        InvoiceItem repareeItem = null;
+        for (final InvoiceItem cur : proposedItems) {
+            if (isRepareeItemForRepairedItem(repairedItem, cur)) {
+                if (repareeItem == null) {
+                    repareeItem = cur;
+                } else {
+                    log.warn("Found multiple reparee item for repaired invoice item " + repairedItem.getId());
+                }
+            }
+        }
+
+        if (repareeItem == null) {
+            log.warn("Could not find reparee item for repaired invoice item " + repairedItem.getId());
+            // Default to no partial repair logic
+            proposedItems.add(candidateRepairItem);
+            return;
+        }
+
+        final BigDecimal partialRepairAmount = candidateRepairItem.getAmount().add(repareeItem.getAmount());
+        if (partialRepairAmount.compareTo(BigDecimal.ZERO) < 0) {
+            final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(candidateRepairItem.getInvoiceId(), candidateRepairItem.getAccountId(), repareeItem.getEndDate(), candidateRepairItem.getEndDate(), partialRepairAmount, candidateRepairItem.getCurrency(), candidateRepairItem.getLinkedItemId());
+            proposedItems.remove(repareeItem);
+            proposedItems.add(repairItem);
+        }
+
+    }
+
+    private static boolean isRepareeItemForRepairedItem(final InvoiceItem repairedInvoiceItem, final InvoiceItem invoiceItem) {
+        return repairedInvoiceItem.getInvoiceItemType().equals(invoiceItem.getInvoiceItemType()) &&
+               repairedInvoiceItem.getSubscriptionId().equals(invoiceItem.getSubscriptionId()) &&
+               repairedInvoiceItem.getStartDate().compareTo(invoiceItem.getStartDate()) == 0 &&
+               // FIXED items have a null end date
+               ((repairedInvoiceItem.getEndDate() == null && invoiceItem.getEndDate() == null) ||
+                (repairedInvoiceItem.getEndDate() != null && invoiceItem.getEndDate() != null && !repairedInvoiceItem.getEndDate().isBefore(invoiceItem.getEndDate()))) &&
+               !repairedInvoiceItem.getId().equals(invoiceItem.getId());
+    }
+
+
+
     // 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