killbill-uncached

analytics: rework BusinessInvoiceDao to work across invoices Reparation

4/12/2013 10:12:23 PM

Details

diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
index 0809d51..4b8d05c 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
@@ -17,12 +17,10 @@
 package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
@@ -107,12 +105,24 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
         final Long tenantRecordId = getTenantRecordId(context);
         final ReportGroup reportGroup = getReportGroup(account.getId(), context);
 
-        // Lookup the invoices for that account
-        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>>();
+        final Map<UUID, BusinessInvoiceModelDao> businessInvoices = new HashMap<UUID, BusinessInvoiceModelDao>();
+        final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
+
+        // All invoice items across all invoices for that account
+        // We need to be able to reference items across multiple invoices
+        final Collection<InvoiceItem> allInvoiceItems = new LinkedList<InvoiceItem>();
 
-        // Create the business invoice and associated business invoice items
+        // Lookup the invoices for that account
         final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
+
+        // Create a convenient mapping invoice_id -> invoice
+        final Map<UUID, Invoice> invoiceIdToInvoiceMappings = new LinkedHashMap<UUID, Invoice>();
+
+        // Create the business invoices
         for (final Invoice invoice : invoices) {
+            invoiceIdToInvoiceMappings.put(invoice.getId(), invoice);
+            allInvoiceItems.addAll(invoice.getInvoiceItems());
+
             final Long invoiceRecordId = getInvoiceRecordId(invoice.getId(), context);
             final AuditLog creationAuditLog = getInvoiceCreationAuditLog(invoice.getId(), context);
             final BusinessInvoiceModelDao businessInvoice = new BusinessInvoiceModelDao(account,
@@ -123,31 +133,43 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                                                                                         tenantRecordId,
                                                                                         reportGroup);
 
-            final List<InvoiceItem> allInvoiceItems = invoice.getInvoiceItems();
-            final Collection<InvoiceItem> sanitizedInvoiceItems = sanitizeInvoiceItems(allInvoiceItems);
-
-            final List<BusinessInvoiceItemBaseModelDao> businessInvoiceItems = new ArrayList<BusinessInvoiceItemBaseModelDao>();
-            for (final InvoiceItem invoiceItem : sanitizedInvoiceItems) {
-                final BusinessInvoiceItemBaseModelDao businessInvoiceItem = createBusinessInvoiceItem(account,
-                                                                                                      invoice,
-                                                                                                      invoiceItem,
-                                                                                                      Collections2.filter(sanitizedInvoiceItems,
-                                                                                                                          new Predicate<InvoiceItem>() {
-                                                                                                                              @Override
-                                                                                                                              public boolean apply(final InvoiceItem input) {
-                                                                                                                                  return !input.getId().equals(invoiceItem.getId());
-                                                                                                                              }
-                                                                                                                          }),
-                                                                                                      context);
-                if (businessInvoiceItem != null) {
-                    businessInvoiceItems.add(businessInvoiceItem);
+            businessInvoices.put(invoice.getId(), businessInvoice);
+        }
+
+        // Sanitize (cherry-pick, merge) the items
+        final Collection<InvoiceItem> sanitizedInvoiceItems = sanitizeInvoiceItems(allInvoiceItems);
+
+        // Create the business invoice items
+        for (final InvoiceItem invoiceItem : sanitizedInvoiceItems) {
+            final Invoice invoice = invoiceIdToInvoiceMappings.get(invoiceItem.getInvoiceId());
+            final BusinessInvoiceItemBaseModelDao businessInvoiceItem = createBusinessInvoiceItem(account,
+                                                                                                  invoice,
+                                                                                                  invoiceItem,
+                                                                                                  Collections2.filter(sanitizedInvoiceItems,
+                                                                                                                      new Predicate<InvoiceItem>() {
+                                                                                                                          @Override
+                                                                                                                          public boolean apply(final InvoiceItem input) {
+                                                                                                                              return !input.getId().equals(invoiceItem.getId());
+                                                                                                                          }
+                                                                                                                      }),
+                                                                                                  accountRecordId,
+                                                                                                  tenantRecordId,
+                                                                                                  reportGroup,
+                                                                                                  context);
+            if (businessInvoiceItem != null) {
+                if (businessInvoiceItems.get(invoice.getId()) == null) {
+                    businessInvoiceItems.put(invoice.getId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
                 }
+                businessInvoiceItems.get(invoice.getId()).add(businessInvoiceItem);
             }
+        }
 
-            businessInvoices.put(businessInvoice, businessInvoiceItems);
+        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessRecords = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>>();
+        for (final BusinessInvoiceModelDao businessInvoiceModelDao : businessInvoices.values()) {
+            businessRecords.put(businessInvoiceModelDao, businessInvoiceItems.get(businessInvoiceModelDao.getInvoiceId()));
         }
 
-        return businessInvoices;
+        return businessRecords;
     }
 
     private void rebuildInvoicesForAccountInTransaction(final BusinessAccountModelDao account,
@@ -190,7 +212,10 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
     private BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
                                                                       final Invoice invoice,
                                                                       final InvoiceItem invoiceItem,
-                                                                      final Collection<InvoiceItem> otherInvoiceItemsOnInvoice,
+                                                                      final Collection<InvoiceItem> otherInvoiceItems,
+                                                                      final Long accountRecordId,
+                                                                      final Long tenantRecordId,
+                                                                      final ReportGroup reportGroup,
                                                                       final TenantContext context) throws AnalyticsRefreshException {
         SubscriptionBundle bundle = null;
         // Subscription and bundle could be null for e.g. credits or adjustments
@@ -210,14 +235,11 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
 
         final Long invoiceItemRecordId = getInvoiceItemRecordId(invoiceItem.getId(), context);
         final AuditLog creationAuditLog = getInvoiceItemCreationAuditLog(invoiceItem.getId(), context);
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
 
         return createBusinessInvoiceItem(account,
                                          invoice,
                                          invoiceItem,
-                                         otherInvoiceItemsOnInvoice,
+                                         otherInvoiceItems,
                                          bundle,
                                          plan,
                                          planPhase,
@@ -233,7 +255,7 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
     BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
                                                               final Invoice invoice,
                                                               final InvoiceItem invoiceItem,
-                                                              final Collection<InvoiceItem> otherInvoiceItemsOnInvoice,
+                                                              final Collection<InvoiceItem> otherInvoiceItems,
                                                               @Nullable final SubscriptionBundle bundle,
                                                               @Nullable final Plan plan,
                                                               @Nullable final PlanPhase planPhase,
@@ -250,14 +272,14 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
             businessInvoiceItemType = BusinessInvoiceItemType.ACCOUNT_CREDIT;
         } else if (isInvoiceItemAdjustmentItem(invoiceItem)) {
             businessInvoiceItemType = BusinessInvoiceItemType.INVOICE_ITEM_ADJUSTMENT;
-        } else if (isInvoiceAdjustmentItem(invoiceItem, otherInvoiceItemsOnInvoice)) {
+        } else if (isInvoiceAdjustmentItem(invoiceItem, otherInvoiceItems)) {
             businessInvoiceItemType = BusinessInvoiceItemType.INVOICE_ADJUSTMENT;
         } else {
             // We don't care
             return null;
         }
 
-        final Boolean revenueRecognizable = isRevenueRecognizable(invoiceItem, otherInvoiceItemsOnInvoice);
+        final Boolean revenueRecognizable = isRevenueRecognizable(invoiceItem, otherInvoiceItems);
 
         final Long secondInvoiceItemRecordId;
         if (invoiceItem instanceof AdjustmentInvoiceItemForRepair) {
@@ -282,25 +304,27 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                                                       reportGroup);
     }
 
-    private Boolean isRevenueRecognizable(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItemsOnInvoice) {
+    private Boolean isRevenueRecognizable(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
         // All items are recognizable except user generated credit (CBA_ADJ and CREDIT_ADJ on their own invoice)
         return !(InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
-                 (otherInvoiceItemsOnInvoice.size() == 1 &&
-                  InvoiceItemType.CREDIT_ADJ.equals(otherInvoiceItemsOnInvoice.iterator().next().getInvoiceItemType()) &&
-                  otherInvoiceItemsOnInvoice.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
+                 (otherInvoiceItems.size() == 1 &&
+                  InvoiceItemType.CREDIT_ADJ.equals(otherInvoiceItems.iterator().next().getInvoiceItemType()) &&
+                  otherInvoiceItems.iterator().next().getInvoiceId().equals(invoiceItem.getInvoiceId()) &&
+                  otherInvoiceItems.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
     }
 
     // Invoice adjustments
     @VisibleForTesting
-    boolean isInvoiceAdjustmentItem(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItemsOnInvoice) {
+    boolean isInvoiceAdjustmentItem(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
         // Either REFUND_ADJ
         return InvoiceItemType.REFUND_ADJ.equals(invoiceItem.getInvoiceItemType()) ||
                // Or invoice level credit, i.e. credit adj, but NOT on its on own invoice
                // Note: the negative credit adj items (internal generation of account level credits) doesn't figure in analytics
                (InvoiceItemType.CREDIT_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
-                !(otherInvoiceItemsOnInvoice.size() == 1 &&
-                  InvoiceItemType.CBA_ADJ.equals(otherInvoiceItemsOnInvoice.iterator().next().getInvoiceItemType()) &&
-                  otherInvoiceItemsOnInvoice.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
+                !(otherInvoiceItems.size() == 1 &&
+                  InvoiceItemType.CBA_ADJ.equals(otherInvoiceItems.iterator().next().getInvoiceItemType()) &&
+                  otherInvoiceItems.iterator().next().getInvoiceId().equals(invoiceItem.getInvoiceId()) &&
+                  otherInvoiceItems.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
     }
 
     // Item adjustments
@@ -321,7 +345,7 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
     }
 
     @VisibleForTesting
-    Collection<InvoiceItem> sanitizeInvoiceItems(final List<InvoiceItem> allInvoiceItems) {
+    Collection<InvoiceItem> sanitizeInvoiceItems(final Collection<InvoiceItem> allInvoiceItems) {
         // Build a convenience mapping between items -> repair_adj items (inverse of linkedItemId)
         final Map<UUID, InvoiceItem> repairedInvoiceItemIdToRepairInvoiceItemMappings = new HashMap<UUID, InvoiceItem>();
         for (final InvoiceItem invoiceItem : allInvoiceItems) {
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java
index 9c776f8..c063011 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java
@@ -47,7 +47,6 @@ import com.google.common.collect.ImmutableList;
 public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
 
     private final UUID accountId = UUID.randomUUID();
-    private final UUID invoiceId = UUID.randomUUID();
     private final UUID bundleId = UUID.randomUUID();
 
     private BusinessInvoiceDao invoiceDao;
@@ -76,11 +75,13 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testRevenueRecognizableClassicAccountCredit() throws Exception {
+        final UUID invoiceId = UUID.randomUUID();
+
         // Classic account credit ($10), from the perspective of the CREDIT_ADJ item
         final BusinessInvoiceItemBaseModelDao businessCreditAdjItem = invoiceDao.createBusinessInvoiceItem(account,
                                                                                                            invoice,
-                                                                                                           createInvoiceItem(InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
-                                                                                                           ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.CBA_ADJ, new BigDecimal("10"))),
+                                                                                                           createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
+                                                                                                           ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10"))),
                                                                                                            null,
                                                                                                            null,
                                                                                                            null,
@@ -96,8 +97,8 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         // Classic account credit ($10), from the perspective of the CBA_ADJ item
         final BusinessInvoiceItemBaseModelDao businessCreditItem = invoiceDao.createBusinessInvoiceItem(account,
                                                                                                         invoice,
-                                                                                                        createInvoiceItem(InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
-                                                                                                        ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10"))),
+                                                                                                        createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
+                                                                                                        ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10"))),
                                                                                                         null,
                                                                                                         null,
                                                                                                         null,
@@ -115,8 +116,8 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         // Invoice adjustment, not to be mixed with credits!
         final BusinessInvoiceItemBaseModelDao businessInvoiceAdjustmentItem = invoiceDao.createBusinessInvoiceItem(account,
                                                                                                                    invoice,
-                                                                                                                   createInvoiceItem(InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
-                                                                                                                   ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.RECURRING, new BigDecimal("10"))),
+                                                                                                                   createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
+                                                                                                                   ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
                                                                                                                    null,
                                                                                                                    null,
                                                                                                                    null,
@@ -134,8 +135,8 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         // Invoice adjustment via refund
         final BusinessInvoiceItemBaseModelDao businessRefundInvoiceAdjustmentItem = invoiceDao.createBusinessInvoiceItem(account,
                                                                                                                          invoice,
-                                                                                                                         createInvoiceItem(InvoiceItemType.REFUND_ADJ, new BigDecimal("-10")),
-                                                                                                                         ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.RECURRING, new BigDecimal("10"))),
+                                                                                                                         createInvoiceItem(invoiceId, InvoiceItemType.REFUND_ADJ, new BigDecimal("-10")),
+                                                                                                                         ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
                                                                                                                          null,
                                                                                                                          null,
                                                                                                                          null,
@@ -153,8 +154,8 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         // Item adjustment
         final BusinessInvoiceItemBaseModelDao businessInvoiceItemAdjustmentItem = invoiceDao.createBusinessInvoiceItem(account,
                                                                                                                        invoice,
-                                                                                                                       createInvoiceItem(InvoiceItemType.ITEM_ADJ, new BigDecimal("-10")),
-                                                                                                                       ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.RECURRING, new BigDecimal("10"))),
+                                                                                                                       createInvoiceItem(invoiceId, InvoiceItemType.ITEM_ADJ, new BigDecimal("-10")),
+                                                                                                                       ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
                                                                                                                        null,
                                                                                                                        null,
                                                                                                                        null,
@@ -172,10 +173,10 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         // System generated account credit
         final BusinessInvoiceItemBaseModelDao businessCBAItem = invoiceDao.createBusinessInvoiceItem(account,
                                                                                                      invoice,
-                                                                                                     createInvoiceItem(InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
-                                                                                                     ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.RECURRING, new BigDecimal("30")),
-                                                                                                                                   createInvoiceItem(InvoiceItemType.REPAIR_ADJ, new BigDecimal("-30")),
-                                                                                                                                   createInvoiceItem(InvoiceItemType.RECURRING, new BigDecimal("20"))),
+                                                                                                     createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
+                                                                                                     ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("30")),
+                                                                                                                                   createInvoiceItem(invoiceId, InvoiceItemType.REPAIR_ADJ, new BigDecimal("-30")),
+                                                                                                                                   createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("20"))),
                                                                                                      null,
                                                                                                      null,
                                                                                                      null,
@@ -193,22 +194,24 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testInvoiceAdjustment() throws Exception {
-        Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(InvoiceItemType.RECURRING),
+        final UUID invoiceId = UUID.randomUUID();
+
+        Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING),
                                                               ImmutableList.<InvoiceItem>of()));
-        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(InvoiceItemType.REFUND_ADJ),
+        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(invoiceId, InvoiceItemType.REFUND_ADJ),
                                                              ImmutableList.<InvoiceItem>of()));
 
-        final InvoiceItem creditAdj = createInvoiceItem(InvoiceItemType.CREDIT_ADJ);
+        final InvoiceItem creditAdj = createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ);
 
         // Account credit
         Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
-                                                              ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
+                                                              ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
 
         Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
-                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate().add(BigDecimal.ONE)))));
+                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate().add(BigDecimal.ONE)))));
         Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
-                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.RECURRING),
-                                                                                           createInvoiceItem(InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
+                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING),
+                                                                                           createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
     }
 
     @Test(groups = "fast")
@@ -218,26 +221,30 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         final LocalDate startDate1 = new LocalDate(2013, 4, 1);
         final LocalDate endDate1 = new LocalDate(2013, 4, 30);
         final BigDecimal amount1 = new BigDecimal("30");
-        final InvoiceItem recurring1 = createInvoiceItem(InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
-        final InvoiceItem repair1 = createInvoiceItem(InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
+        final UUID originalInvoice1 = UUID.randomUUID();
+        final UUID reparationInvoice1 = UUID.randomUUID();
+        final InvoiceItem recurring1 = createInvoiceItem(originalInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
+        final InvoiceItem repair1 = createInvoiceItem(originalInvoice1, InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
         final LocalDate reparationEndDate1 = new LocalDate(2013, 4, 10);
         final BigDecimal reparationAmount1 = new BigDecimal("10");
-        final InvoiceItem reparation1 = createInvoiceItem(InvoiceItemType.RECURRING, subscriptionId1, startDate1, reparationEndDate1, reparationAmount1, null);
+        final InvoiceItem reparation1 = createInvoiceItem(reparationInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, reparationEndDate1, reparationAmount1, null);
 
         final UUID subscriptionId2 = UUID.randomUUID();
         final LocalDate startDate2 = new LocalDate(2013, 4, 10);
         final LocalDate endDate2 = new LocalDate(2013, 4, 30);
         final BigDecimal amount2 = new BigDecimal("20");
-        final InvoiceItem recurring2 = createInvoiceItem(InvoiceItemType.RECURRING, subscriptionId2, startDate2, endDate2, amount2, null);
-        final InvoiceItem repair2 = createInvoiceItem(InvoiceItemType.REPAIR_ADJ, subscriptionId2, startDate2, endDate2, amount2.negate(), recurring2.getId());
+        final UUID originalInvoice2 = UUID.randomUUID();
+        final UUID reparationInvoice2 = UUID.randomUUID();
+        final InvoiceItem recurring2 = createInvoiceItem(originalInvoice2, InvoiceItemType.RECURRING, subscriptionId2, startDate2, endDate2, amount2, null);
+        final InvoiceItem repair2 = createInvoiceItem(originalInvoice2, InvoiceItemType.REPAIR_ADJ, subscriptionId2, startDate2, endDate2, amount2.negate(), recurring2.getId());
         final LocalDate reparationEndDate2 = new LocalDate(2013, 4, 15);
         final BigDecimal reparationAmount2 = new BigDecimal("5");
-        final InvoiceItem reparation2 = createInvoiceItem(InvoiceItemType.RECURRING, subscriptionId2, startDate2, reparationEndDate2, reparationAmount2, null);
+        final InvoiceItem reparation2 = createInvoiceItem(reparationInvoice2, InvoiceItemType.RECURRING, subscriptionId2, startDate2, reparationEndDate2, reparationAmount2, null);
 
         final UUID externalChargeSubscriptionId = UUID.randomUUID();
         final LocalDate externalStartDate = new LocalDate(2012, 1, 1);
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
-        final InvoiceItem externalCharge = createInvoiceItem(InvoiceItemType.EXTERNAL_CHARGE, externalChargeSubscriptionId, externalStartDate, null, externalChargeAmount, null);
+        final InvoiceItem externalCharge = createInvoiceItem(UUID.randomUUID(), InvoiceItemType.EXTERNAL_CHARGE, externalChargeSubscriptionId, externalStartDate, null, externalChargeAmount, null);
 
         final Collection<InvoiceItem> sanitizedInvoiceItems = invoiceDao.sanitizeInvoiceItems(ImmutableList.<InvoiceItem>of(recurring1, repair1, reparation1, recurring2, repair2, reparation2, externalCharge));
         Assert.assertEquals(sanitizedInvoiceItems.size(), 2 + 2 + 1);
@@ -270,15 +277,16 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
         }
     }
 
-    private InvoiceItem createInvoiceItem(final InvoiceItemType type) {
-        return createInvoiceItem(type, BigDecimal.TEN);
+    private InvoiceItem createInvoiceItem(final UUID invoiceId, final InvoiceItemType type) {
+        return createInvoiceItem(invoiceId, type, BigDecimal.TEN);
     }
 
-    private InvoiceItem createInvoiceItem(final InvoiceItemType type, final BigDecimal amount) {
-        return createInvoiceItem(type, UUID.randomUUID(), new LocalDate(2013, 1, 2), new LocalDate(2013, 2, 5), amount, null);
+    private InvoiceItem createInvoiceItem(final UUID invoiceId, final InvoiceItemType type, final BigDecimal amount) {
+        return createInvoiceItem(invoiceId, type, UUID.randomUUID(), new LocalDate(2013, 1, 2), new LocalDate(2013, 2, 5), amount, null);
     }
 
-    private InvoiceItem createInvoiceItem(final InvoiceItemType invoiceItemType,
+    private InvoiceItem createInvoiceItem(final UUID invoiceId,
+                                          final InvoiceItemType invoiceItemType,
                                           final UUID subscriptionId,
                                           final LocalDate startDate,
                                           final LocalDate endDate,