killbill-memoizeit

Changes

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSqlProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSqlProvider.java
index 6c8f872..ff980a6 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSqlProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSqlProvider.java
@@ -18,17 +18,15 @@ package com.ning.billing.analytics.dao;
 
 import org.skife.jdbi.v2.IDBI;
 
-import com.google.inject.Inject;
 import com.google.inject.Provider;
 
 public class BusinessSqlProvider<T> implements Provider<T> {
 
-    @Inject
-    private IDBI dbi;
-
+    private final IDBI dbi;
     private final Class clazz;
 
-    public BusinessSqlProvider(final Class<T> clazz) {
+    public BusinessSqlProvider(final IDBI dbi, final Class<T> clazz) {
+        this.dbi = dbi;
         this.clazz = clazz;
     }
 
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index 0867140..4d96f0a 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -17,6 +17,10 @@
 package com.ning.billing.analytics.setup;
 
 import org.skife.config.ConfigSource;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.tweak.TransactionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.analytics.BusinessAccountDao;
@@ -47,15 +51,28 @@ import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
 import com.ning.billing.analytics.dao.DefaultAnalyticsDao;
 import com.ning.billing.analytics.dao.DefaultAnalyticsSanityDao;
+import com.ning.billing.util.dao.DateTimeArgumentFactory;
+import com.ning.billing.util.dao.DateTimeZoneArgumentFactory;
+import com.ning.billing.util.dao.EnumArgumentFactory;
+import com.ning.billing.util.dao.LocalDateArgumentFactory;
+import com.ning.billing.util.dao.UUIDArgumentFactory;
+import com.ning.billing.util.dao.UuidMapper;
 
 import com.google.inject.AbstractModule;
+import com.mchange.v2.c3p0.ComboPooledDataSource;
 
 public class AnalyticsModule extends AbstractModule {
 
+    private static final Logger logger = LoggerFactory.getLogger(AnalyticsModule.class);
+
+    public static final String ANALYTICS_DBI_CONFIG_STRING = "com.ning.billing.analytics.dbi.";
+
     protected final ConfigSource configSource;
+    protected final DBI dbi;
 
     public AnalyticsModule(final ConfigSource configSource) {
         this.configSource = configSource;
+        this.dbi = createDBI(configSource);
     }
 
     @Override
@@ -81,26 +98,54 @@ public class AnalyticsModule extends AbstractModule {
 
     protected void installAnalyticsDao() {
         bind(AnalyticsDao.class).to(DefaultAnalyticsDao.class).asEagerSingleton();
-        bind(AnalyticsSanityDao.class).to(DefaultAnalyticsSanityDao.class).asEagerSingleton();
+        bind(AnalyticsSanityDao.class).toInstance(new DefaultAnalyticsSanityDao(dbi));
         bind(BusinessSubscriptionTransitionDao.class).asEagerSingleton();
         bind(BusinessAccountDao.class).asEagerSingleton();
         bind(BusinessTagDao.class).asEagerSingleton();
     }
 
     protected void installAnalyticsSqlDao() {
-        bind(BusinessAccountSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountSqlDao>(BusinessAccountSqlDao.class));
-        bind(BusinessAccountTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountTagSqlDao>(BusinessAccountTagSqlDao.class));
-        bind(BusinessAccountFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountFieldSqlDao>(BusinessAccountFieldSqlDao.class));
-        bind(BusinessInvoiceFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceFieldSqlDao>(BusinessInvoiceFieldSqlDao.class));
-        bind(BusinessInvoiceItemSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceItemSqlDao>(BusinessInvoiceItemSqlDao.class));
-        bind(BusinessInvoicePaymentFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentFieldSqlDao>(BusinessInvoicePaymentFieldSqlDao.class));
-        bind(BusinessInvoicePaymentSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentSqlDao>(BusinessInvoicePaymentSqlDao.class));
-        bind(BusinessInvoicePaymentTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentTagSqlDao>(BusinessInvoicePaymentTagSqlDao.class));
-        bind(BusinessInvoiceSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceSqlDao>(BusinessInvoiceSqlDao.class));
-        bind(BusinessInvoiceTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceTagSqlDao>(BusinessInvoiceTagSqlDao.class));
-        bind(BusinessOverdueStatusSqlDao.class).toProvider(new BusinessSqlProvider<BusinessOverdueStatusSqlDao>(BusinessOverdueStatusSqlDao.class));
-        bind(BusinessSubscriptionTransitionFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionFieldSqlDao>(BusinessSubscriptionTransitionFieldSqlDao.class));
-        bind(BusinessSubscriptionTransitionSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionSqlDao>(BusinessSubscriptionTransitionSqlDao.class));
-        bind(BusinessSubscriptionTransitionTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionTagSqlDao>(BusinessSubscriptionTransitionTagSqlDao.class));
+        bind(BusinessAccountSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountSqlDao>(dbi, BusinessAccountSqlDao.class));
+        bind(BusinessAccountTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountTagSqlDao>(dbi, BusinessAccountTagSqlDao.class));
+        bind(BusinessAccountFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessAccountFieldSqlDao>(dbi, BusinessAccountFieldSqlDao.class));
+        bind(BusinessInvoiceFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceFieldSqlDao>(dbi, BusinessInvoiceFieldSqlDao.class));
+        bind(BusinessInvoiceItemSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceItemSqlDao>(dbi, BusinessInvoiceItemSqlDao.class));
+        bind(BusinessInvoicePaymentFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentFieldSqlDao>(dbi, BusinessInvoicePaymentFieldSqlDao.class));
+        bind(BusinessInvoicePaymentSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentSqlDao>(dbi, BusinessInvoicePaymentSqlDao.class));
+        bind(BusinessInvoicePaymentTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoicePaymentTagSqlDao>(dbi, BusinessInvoicePaymentTagSqlDao.class));
+        bind(BusinessInvoiceSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceSqlDao>(dbi, BusinessInvoiceSqlDao.class));
+        bind(BusinessInvoiceTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessInvoiceTagSqlDao>(dbi, BusinessInvoiceTagSqlDao.class));
+        bind(BusinessOverdueStatusSqlDao.class).toProvider(new BusinessSqlProvider<BusinessOverdueStatusSqlDao>(dbi, BusinessOverdueStatusSqlDao.class));
+        bind(BusinessSubscriptionTransitionFieldSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionFieldSqlDao>(dbi, BusinessSubscriptionTransitionFieldSqlDao.class));
+        bind(BusinessSubscriptionTransitionSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionSqlDao>(dbi, BusinessSubscriptionTransitionSqlDao.class));
+        bind(BusinessSubscriptionTransitionTagSqlDao.class).toProvider(new BusinessSqlProvider<BusinessSubscriptionTransitionTagSqlDao>(dbi, BusinessSubscriptionTransitionTagSqlDao.class));
+    }
+
+    private DBI createDBI(final ConfigSource configSource) {
+        final ComboPooledDataSource dataSource = new ComboPooledDataSource();
+        dataSource.setJdbcUrl(configSource.getString(ANALYTICS_DBI_CONFIG_STRING + "url"));
+        dataSource.setUser(configSource.getString(ANALYTICS_DBI_CONFIG_STRING + "user"));
+        dataSource.setPassword(configSource.getString(ANALYTICS_DBI_CONFIG_STRING + "password"));
+        dataSource.setMinPoolSize(1);
+        dataSource.setMaxPoolSize(10);
+        dataSource.setCheckoutTimeout(10 * 1000);
+        dataSource.setMaxIdleTime(60 * 60);
+        dataSource.setMaxConnectionAge(0);
+        dataSource.setIdleConnectionTestPeriod(5 * 60);
+
+        final DBI dbi = new DBI(dataSource);
+        dbi.registerArgumentFactory(new UUIDArgumentFactory());
+        dbi.registerArgumentFactory(new DateTimeZoneArgumentFactory());
+        dbi.registerArgumentFactory(new DateTimeArgumentFactory());
+        dbi.registerArgumentFactory(new LocalDateArgumentFactory());
+        dbi.registerArgumentFactory(new EnumArgumentFactory());
+        dbi.registerMapper(new UuidMapper());
+        try {
+            dbi.setTransactionHandler((TransactionHandler) Class.forName("com.ning.jetty.jdbi.RestartTransactionRunner").newInstance());
+        } catch (Exception e) {
+            logger.warn("Unable to register transaction handler");
+        }
+
+        return dbi;
     }
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestSuiteWithEmbeddedDB.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestSuiteWithEmbeddedDB.java
index 3629261..fc15e87 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestSuiteWithEmbeddedDB.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestSuiteWithEmbeddedDB.java
@@ -40,7 +40,9 @@ import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
 import com.ning.billing.analytics.glue.TestAnalyticsModuleWithEmbeddedDB;
+import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.dbi.DBTestingHelper;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
@@ -129,6 +131,10 @@ public abstract class AnalyticsTestSuiteWithEmbeddedDB extends GuicyKillbillTest
 
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
+        configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "url", getDBTestingHelper().getJdbcConnectionString());
+        configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "user", DBTestingHelper.USERNAME);
+        configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "password", DBTestingHelper.PASSWORD);
+
         final Injector injector = Guice.createInjector(new TestAnalyticsModuleWithEmbeddedDB(configSource));
         injector.injectMembers(this);
     }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 9c83c43..bdef87f 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -40,6 +40,7 @@ import com.ning.billing.account.api.AccountService;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.analytics.api.user.DefaultAnalyticsUserApi;
+import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.api.TestApiListener;
 import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.api.TestListenerStatus;
@@ -57,6 +58,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.dbi.DBTestingHelper;
 import com.ning.billing.entitlement.api.EntitlementService;
 import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
 import com.ning.billing.entitlement.api.transfer.EntitlementTransferApi;
@@ -226,6 +228,10 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
 
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
+        configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "url", getDBTestingHelper().getJdbcConnectionString());
+        configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "user", DBTestingHelper.USERNAME);
+        configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "password", DBTestingHelper.PASSWORD);
+
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new BeatrixIntegrationModule(configSource));
         g.injectMembers(this);
         busHandler = new TestApiListener(this);
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 8e89f8e..32e8bde 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
@@ -16,27 +16,34 @@
 
 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;
 
-import javax.annotation.Nullable;
-
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
 import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao.BusinessInvoiceItemType;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
 import com.ning.billing.util.audit.AuditLog;
@@ -46,6 +53,10 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+
 public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
 
     private final BusinessAccountDao businessAccountDao;
@@ -109,13 +120,21 @@ 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 : invoice.getInvoiceItems()) {
+            for (final InvoiceItem invoiceItem : sanitizedInvoiceItems) {
                 final BusinessInvoiceItemBaseModelDao businessInvoiceItem = createBusinessInvoiceItem(account,
                                                                                                       invoice,
                                                                                                       invoiceItem,
-                                                                                                      // TODO Will be used for REPAIR_ADJ
-                                                                                                      null,
+                                                                                                      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);
@@ -168,7 +187,7 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
     private BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
                                                                       final Invoice invoice,
                                                                       final InvoiceItem invoiceItem,
-                                                                      @Nullable final Long secondInvoiceItemRecordId,
+                                                                      final Collection<InvoiceItem> otherInvoiceItemsOnInvoice,
                                                                       final TenantContext context) throws AnalyticsRefreshException {
         SubscriptionBundle bundle = null;
         // Subscription and bundle could be null for e.g. credits or adjustments
@@ -192,10 +211,35 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
         final Long tenantRecordId = getTenantRecordId(context);
         final ReportGroup reportGroup = getReportGroup(account.getId(), context);
 
+        final BusinessInvoiceItemType businessInvoiceItemType;
+        if (isCharge(invoiceItem)) {
+            businessInvoiceItemType = BusinessInvoiceItemType.CHARGE;
+        } else if (isAccountCreditItem(invoiceItem)) {
+            businessInvoiceItemType = BusinessInvoiceItemType.ACCOUNT_CREDIT;
+        } else if (isInvoiceItemAdjustmentItem(invoiceItem)) {
+            businessInvoiceItemType = BusinessInvoiceItemType.INVOICE_ITEM_ADJUSTMENT;
+        } else if (isInvoiceAdjustmentItem(invoiceItem, otherInvoiceItemsOnInvoice)) {
+            businessInvoiceItemType = BusinessInvoiceItemType.INVOICE_ADJUSTMENT;
+        } else {
+            // We don't care
+            return null;
+        }
+
+        final Boolean revenueRecognizable = isRevenueRecognizable(invoiceItem);
+
+        final Long secondInvoiceItemRecordId;
+        if (invoiceItem instanceof AdjustmentInvoiceItemForRepair) {
+            secondInvoiceItemRecordId = getInvoiceItemRecordId(((AdjustmentInvoiceItemForRepair) invoiceItem).getSecondId(), context);
+        } else {
+            secondInvoiceItemRecordId = null;
+        }
+
         return BusinessInvoiceItemBaseModelDao.create(account,
                                                       accountRecordId,
                                                       invoice,
                                                       invoiceItem,
+                                                      revenueRecognizable,
+                                                      businessInvoiceItemType,
                                                       invoiceItemRecordId,
                                                       secondInvoiceItemRecordId,
                                                       bundle,
@@ -205,4 +249,201 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                                                       tenantRecordId,
                                                       reportGroup);
     }
+
+    @VisibleForTesting
+    Boolean isRevenueRecognizable(final InvoiceItem invoiceItem) {
+        // REFUND_ADJ are recognizable because associated with a payment
+        return !InvoiceItemType.CREDIT_ADJ.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    // Invoice adjustments
+    @VisibleForTesting
+    boolean isInvoiceAdjustmentItem(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItemsOnInvoice) {
+        // 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));
+    }
+
+    // Item adjustments
+    private boolean isInvoiceItemAdjustmentItem(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    // Account credits, used or consumed
+    private boolean isAccountCreditItem(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    // Regular line item (charges)
+    private boolean isCharge(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.EXTERNAL_CHARGE.equals(invoiceItem.getInvoiceItemType()) ||
+               InvoiceItemType.FIXED.equals(invoiceItem.getInvoiceItemType()) ||
+               InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    @VisibleForTesting
+    Collection<InvoiceItem> sanitizeInvoiceItems(final List<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) {
+            if (InvoiceItemType.REPAIR_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+                repairedInvoiceItemIdToRepairInvoiceItemMappings.put(invoiceItem.getLinkedItemId(), invoiceItem);
+            }
+        }
+
+        // Now find the "reparation" items, i.e. the ones which correspond to the repaired items
+        final Map<UUID, InvoiceItem> reparationInvoiceItemIdToRepairItemMappings = new LinkedHashMap<UUID, InvoiceItem>();
+        for (final InvoiceItem repairedInvoiceItem : repairedInvoiceItemIdToRepairInvoiceItemMappings.values()) {
+            InvoiceItem reparationItem = null;
+            for (final InvoiceItem invoiceItem : allInvoiceItems) {
+                // Try to find the matching "reparation" item
+                if (repairedInvoiceItem.getInvoiceItemType().equals(invoiceItem.getInvoiceItemType()) &&
+                    repairedInvoiceItem.getSubscriptionId().equals(invoiceItem.getSubscriptionId()) &&
+                    repairedInvoiceItem.getStartDate().compareTo(invoiceItem.getStartDate()) == 0 &&
+                    !repairedInvoiceItem.getEndDate().isBefore(invoiceItem.getEndDate())) {
+                    if (reparationItem == null) {
+                        reparationItem = invoiceItem;
+                    } else {
+                        logService.log(LogService.LOG_ERROR, "Found multiple reparation items matching the repair item id " + repairedInvoiceItem.getId() + " - this should never happen!");
+                    }
+                }
+            }
+
+            if (reparationItem != null) {
+                reparationInvoiceItemIdToRepairItemMappings.put(reparationItem.getId(), repairedInvoiceItemIdToRepairInvoiceItemMappings.get(repairedInvoiceItem.getId()));
+            } else {
+                logService.log(LogService.LOG_ERROR, "Could not find the reparation item for the repair item id " + repairedInvoiceItem.getId() + " - this should never happen!");
+            }
+        }
+
+        // Filter the invoice items for analytics
+        final Collection<InvoiceItem> invoiceItemsForAnalytics = new LinkedList<InvoiceItem>();
+        for (final InvoiceItem invoiceItem : allInvoiceItems) {
+            if (InvoiceItemType.REPAIR_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+                // We don't care, we'll create a special item for it below
+            } else if (reparationInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
+                // We do care - this is a reparation item. Create an item adjustment for it
+                final InvoiceItem repairInvoiceItem = reparationInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
+                final InvoiceItem reparationInvoiceItem = invoiceItem;
+                invoiceItemsForAnalytics.add(new AdjustmentInvoiceItemForRepair(repairInvoiceItem, reparationInvoiceItem));
+            } else {
+                invoiceItemsForAnalytics.add(invoiceItem);
+            }
+        }
+
+        return invoiceItemsForAnalytics;
+    }
+
+    private class AdjustmentInvoiceItemForRepair implements InvoiceItem {
+
+        private final InvoiceItem repairInvoiceItem;
+        private final InvoiceItem reparationInvoiceItem;
+
+        private AdjustmentInvoiceItemForRepair(final InvoiceItem repairInvoiceItem,
+                                               final InvoiceItem reparationInvoiceItem) {
+            this.repairInvoiceItem = repairInvoiceItem;
+            this.reparationInvoiceItem = reparationInvoiceItem;
+        }
+
+        @Override
+        public InvoiceItemType getInvoiceItemType() {
+            return InvoiceItemType.ITEM_ADJ;
+        }
+
+        @Override
+        public UUID getInvoiceId() {
+            return repairInvoiceItem.getInvoiceId();
+        }
+
+        @Override
+        public UUID getAccountId() {
+            return repairInvoiceItem.getAccountId();
+        }
+
+        @Override
+        public LocalDate getStartDate() {
+            return repairInvoiceItem.getStartDate();
+        }
+
+        @Override
+        public LocalDate getEndDate() {
+            return repairInvoiceItem.getStartDate();
+        }
+
+        @Override
+        public BigDecimal getAmount() {
+            return reparationInvoiceItem.getAmount().add(repairInvoiceItem.getAmount());
+        }
+
+        @Override
+        public Currency getCurrency() {
+            return repairInvoiceItem.getCurrency();
+        }
+
+        @Override
+        public String getDescription() {
+            return null;
+        }
+
+        @Override
+        public UUID getBundleId() {
+            return null;
+        }
+
+        @Override
+        public UUID getSubscriptionId() {
+            return null;
+        }
+
+        @Override
+        public String getPlanName() {
+            return null;
+        }
+
+        @Override
+        public String getPhaseName() {
+            return null;
+        }
+
+        @Override
+        public BigDecimal getRate() {
+            return null;
+        }
+
+        @Override
+        public UUID getLinkedItemId() {
+            return repairInvoiceItem.getLinkedItemId();
+        }
+
+        @Override
+        public int compareTo(final InvoiceItem o) {
+            return repairInvoiceItem.compareTo(o);
+        }
+
+        @Override
+        public UUID getId() {
+            // We pretend to be the repair, the reparation item record id
+            // will be available as secondId
+            return repairInvoiceItem.getId();
+        }
+
+        public UUID getSecondId() {
+            return reparationInvoiceItem.getId();
+        }
+
+        @Override
+        public DateTime getCreatedDate() {
+            return repairInvoiceItem.getCreatedDate();
+        }
+
+        @Override
+        public DateTime getUpdatedDate() {
+            return repairInvoiceItem.getUpdatedDate();
+        }
+    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java
index a437645..2114226 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java
@@ -34,6 +34,7 @@ public class BusinessInvoiceAdjustmentModelDao extends BusinessInvoiceItemBaseMo
                                              final Long accountRecordId,
                                              final Invoice invoice,
                                              final InvoiceItem invoiceItem,
+                                             final Boolean revenueRecognizable,
                                              final Long invoiceItemRecordId,
                                              final Long secondInvoiceItemRecordId,
                                              @Nullable final SubscriptionBundle bundle,
@@ -46,6 +47,7 @@ public class BusinessInvoiceAdjustmentModelDao extends BusinessInvoiceItemBaseMo
               accountRecordId,
               invoice,
               invoiceItem,
+              revenueRecognizable,
               invoiceItemRecordId,
               secondInvoiceItemRecordId,
               bundle,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java
index fab5ff4..bbc2af6 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java
@@ -34,6 +34,7 @@ public class BusinessInvoiceItemAdjustmentModelDao extends BusinessInvoiceItemBa
                                                  final Long accountRecordId,
                                                  final Invoice invoice,
                                                  final InvoiceItem invoiceItem,
+                                                 final Boolean revenueRecognizable,
                                                  final Long invoiceItemRecordId,
                                                  final Long secondInvoiceItemRecordId,
                                                  @Nullable final SubscriptionBundle bundle,
@@ -46,6 +47,7 @@ public class BusinessInvoiceItemAdjustmentModelDao extends BusinessInvoiceItemBa
               accountRecordId,
               invoice,
               invoiceItem,
+              revenueRecognizable,
               invoiceItemRecordId,
               secondInvoiceItemRecordId,
               bundle,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java
index 4059810..33ca531 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java
@@ -30,7 +30,6 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.util.audit.AuditLog;
 
 public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBase {
@@ -71,10 +70,19 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
     private String currency;
     private UUID linkedItemId;
 
+    public enum BusinessInvoiceItemType {
+        INVOICE_ADJUSTMENT,
+        INVOICE_ITEM_ADJUSTMENT,
+        ACCOUNT_CREDIT,
+        CHARGE
+    }
+
     public static BusinessInvoiceItemBaseModelDao create(final Account account,
                                                          final Long accountRecordId,
                                                          final Invoice invoice,
                                                          final InvoiceItem invoiceItem,
+                                                         final Boolean revenueRecognizable,
+                                                         final BusinessInvoiceItemType businessInvoiceItemType,
                                                          final Long invoiceItemRecordId,
                                                          final Long secondInvoiceItemRecordId,
                                                          @Nullable final SubscriptionBundle bundle,
@@ -83,11 +91,12 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
                                                          final AuditLog creationAuditLog,
                                                          final Long tenantRecordId,
                                                          @Nullable final ReportGroup reportGroup) {
-        if (InvoiceItemType.REFUND_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+        if (BusinessInvoiceItemType.INVOICE_ADJUSTMENT.equals(businessInvoiceItemType)) {
             return new BusinessInvoiceAdjustmentModelDao(account,
                                                          accountRecordId,
                                                          invoice,
                                                          invoiceItem,
+                                                         revenueRecognizable,
                                                          invoiceItemRecordId,
                                                          secondInvoiceItemRecordId,
                                                          bundle,
@@ -96,13 +105,12 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
                                                          creationAuditLog,
                                                          tenantRecordId,
                                                          reportGroup);
-        } else if (InvoiceItemType.EXTERNAL_CHARGE.equals(invoiceItem.getInvoiceItemType()) ||
-                   InvoiceItemType.FIXED.equals(invoiceItem.getInvoiceItemType()) ||
-                   InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType())) {
+        } else if (BusinessInvoiceItemType.CHARGE.equals(businessInvoiceItemType)) {
             return new BusinessInvoiceItemModelDao(account,
                                                    accountRecordId,
                                                    invoice,
                                                    invoiceItem,
+                                                   revenueRecognizable,
                                                    invoiceItemRecordId,
                                                    secondInvoiceItemRecordId,
                                                    bundle,
@@ -111,11 +119,12 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
                                                    creationAuditLog,
                                                    tenantRecordId,
                                                    reportGroup);
-        } else if (InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+        } else if (BusinessInvoiceItemType.INVOICE_ITEM_ADJUSTMENT.equals(businessInvoiceItemType)) {
             return new BusinessInvoiceItemAdjustmentModelDao(account,
                                                              accountRecordId,
                                                              invoice,
                                                              invoiceItem,
+                                                             revenueRecognizable,
                                                              invoiceItemRecordId,
                                                              secondInvoiceItemRecordId,
                                                              bundle,
@@ -124,12 +133,12 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
                                                              creationAuditLog,
                                                              tenantRecordId,
                                                              reportGroup);
-        } else if (InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType()) ||
-                   InvoiceItemType.CREDIT_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+        } else if (BusinessInvoiceItemType.ACCOUNT_CREDIT.equals(businessInvoiceItemType)) {
             return new BusinessInvoiceItemCreditModelDao(account,
                                                          accountRecordId,
                                                          invoice,
                                                          invoiceItem,
+                                                         revenueRecognizable,
                                                          invoiceItemRecordId,
                                                          secondInvoiceItemRecordId,
                                                          bundle,
@@ -228,6 +237,7 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
                                            final Long accountRecordId,
                                            final Invoice invoice,
                                            final InvoiceItem invoiceItem,
+                                           final Boolean revenueRecognizable,
                                            final Long invoiceItemRecordId,
                                            final Long secondInvoiceItemRecordId,
                                            @Nullable final SubscriptionBundle bundle,
@@ -251,7 +261,7 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
              invoice.getOriginalChargedAmount(),
              invoice.getCreditAdjAmount(),
              invoiceItem.getInvoiceItemType().toString(),
-             null /* TODO */,
+             revenueRecognizable,
              bundle == null ? null : bundle.getExternalKey(),
              plan != null ? plan.getProduct().getName() : null,
              plan != null ? plan.getProduct().getCatalogName() : null,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java
index 1d64378..bf26129 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java
@@ -34,6 +34,7 @@ public class BusinessInvoiceItemCreditModelDao extends BusinessInvoiceItemBaseMo
                                              final Long accountRecordId,
                                              final Invoice invoice,
                                              final InvoiceItem invoiceItem,
+                                             final Boolean revenueRecognizable,
                                              final Long invoiceItemRecordId,
                                              final Long secondInvoiceItemRecordId,
                                              @Nullable final SubscriptionBundle bundle,
@@ -46,6 +47,7 @@ public class BusinessInvoiceItemCreditModelDao extends BusinessInvoiceItemBaseMo
               accountRecordId,
               invoice,
               invoiceItem,
+              revenueRecognizable,
               invoiceItemRecordId,
               secondInvoiceItemRecordId,
               bundle,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java
index 3558946..1e5602a 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java
@@ -34,6 +34,7 @@ public class BusinessInvoiceItemModelDao extends BusinessInvoiceItemBaseModelDao
                                        final Long accountRecordId,
                                        final Invoice invoice,
                                        final InvoiceItem invoiceItem,
+                                       final Boolean revenueRecognizable,
                                        final Long invoiceItemRecordId,
                                        final Long secondInvoiceItemRecordId,
                                        @Nullable final SubscriptionBundle bundle,
@@ -46,6 +47,7 @@ public class BusinessInvoiceItemModelDao extends BusinessInvoiceItemBaseModelDao
               accountRecordId,
               invoice,
               invoiceItem,
+              revenueRecognizable,
               invoiceItemRecordId,
               secondInvoiceItemRecordId,
               bundle,
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessModelDaoBase.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessModelDaoBase.java
index a36b589..8b64215 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessModelDaoBase.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessModelDaoBase.java
@@ -148,9 +148,6 @@ public abstract class BusinessModelDaoBase {
 
         final BusinessModelDaoBase that = (BusinessModelDaoBase) o;
 
-        if (DEFAULT_REPORT_GROUP != null ? !DEFAULT_REPORT_GROUP.equals(that.DEFAULT_REPORT_GROUP) : that.DEFAULT_REPORT_GROUP != null) {
-            return false;
-        }
         if (accountExternalKey != null ? !accountExternalKey.equals(that.accountExternalKey) : that.accountExternalKey != null) {
             return false;
         }
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
index 97c0729..fd1d2a3 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
@@ -23,6 +23,8 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 
@@ -49,6 +51,7 @@ import com.ning.billing.junction.api.Blockable.Type;
 import com.ning.billing.junction.api.BlockingState;
 import com.ning.billing.osgi.bundles.analytics.api.BusinessEntityBase;
 import com.ning.billing.osgi.bundles.analytics.dao.TestCallContext;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao.BusinessInvoiceItemType;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
 import com.ning.billing.payment.api.Payment;
@@ -72,6 +75,8 @@ import com.google.common.collect.ImmutableList;
 
 public abstract class AnalyticsTestSuiteNoDB {
 
+    protected final Logger logger = LoggerFactory.getLogger(AnalyticsTestSuiteNoDB.class);
+
     protected final Long accountRecordId = 1L;
     protected final Long subscriptionEventRecordId = 2L;
     protected final Long invoiceRecordId = 3L;
@@ -84,6 +89,7 @@ public abstract class AnalyticsTestSuiteNoDB {
     protected final Long tenantRecordId = 9L;
 
     protected final ReportGroup reportGroup = ReportGroup.partner;
+    protected final BusinessInvoiceItemType invoiceItemType = BusinessInvoiceItemType.INVOICE_ITEM_ADJUSTMENT;
 
     protected Account account;
     protected SubscriptionBundle bundle;
@@ -94,6 +100,7 @@ public abstract class AnalyticsTestSuiteNoDB {
     protected BlockingState blockingState;
     protected Invoice invoice;
     protected InvoiceItem invoiceItem;
+    protected Boolean recognizable;
     protected InvoicePayment invoicePayment;
     protected PaymentAttempt paymentAttempt;
     protected PaymentMethod paymentMethod;
@@ -239,6 +246,8 @@ public abstract class AnalyticsTestSuiteNoDB {
         Mockito.when(invoiceItem.getCreatedDate()).thenReturn(new DateTime(2016, 1, 22, 10, 56, 51, DateTimeZone.UTC));
         final UUID invoiceItemId = invoiceItem.getId();
 
+        recognizable = false;
+
         final UUID invoiceId = UUID.randomUUID();
 
         invoicePayment = Mockito.mock(InvoicePayment.class);
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java
index f59b325..51fdbb1 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java
@@ -40,6 +40,8 @@ public class TestBusinessInvoice extends AnalyticsTestSuiteNoDB {
                                                                                                                accountRecordId,
                                                                                                                invoice,
                                                                                                                invoiceItem,
+                                                                                                               recognizable,
+                                                                                                               invoiceItemType,
                                                                                                                invoiceItemRecordId,
                                                                                                                secondInvoiceItemRecordId,
                                                                                                                bundle,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoiceItem.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoiceItem.java
index bd857d5..c7c2c55 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoiceItem.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoiceItem.java
@@ -30,6 +30,8 @@ public class TestBusinessInvoiceItem extends AnalyticsTestSuiteNoDB {
                                                                                                                accountRecordId,
                                                                                                                invoice,
                                                                                                                invoiceItem,
+                                                                                                               recognizable,
+                                                                                                               invoiceItemType,
                                                                                                                invoiceItemRecordId,
                                                                                                                secondInvoiceItemRecordId,
                                                                                                                bundle,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java
index c6b4773..b741810 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java
@@ -78,6 +78,8 @@ public class TestBusinessSnapshot extends AnalyticsTestSuiteNoDB {
                                                                                                                accountRecordId,
                                                                                                                invoice,
                                                                                                                invoiceItem,
+                                                                                                               recognizable,
+                                                                                                               invoiceItemType,
                                                                                                                invoiceItemRecordId,
                                                                                                                secondInvoiceItemRecordId,
                                                                                                                bundle,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java
index bab0f9c..1d867e8 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java
@@ -29,6 +29,7 @@ public class TestBusinessInvoiceItemModelDao extends AnalyticsTestSuiteNoDB {
                                                                                                 accountRecordId,
                                                                                                 invoice,
                                                                                                 invoiceItem,
+                                                                                                recognizable,
                                                                                                 invoiceItemRecordId,
                                                                                                 secondInvoiceItemRecordId,
                                                                                                 null,
@@ -53,6 +54,7 @@ public class TestBusinessInvoiceItemModelDao extends AnalyticsTestSuiteNoDB {
                                                                                                 accountRecordId,
                                                                                                 invoice,
                                                                                                 invoiceItem,
+                                                                                                recognizable,
                                                                                                 invoiceItemRecordId,
                                                                                                 secondInvoiceItemRecordId,
                                                                                                 bundle,
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 5a50590..60faf8d 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
@@ -1,4 +1,200 @@
+/*
+ * 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.osgi.bundles.analytics.dao;
 
-public class TestBusinessInvoiceDao {
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.sql.DataSource;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+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;
+
+    @Override
+    @BeforeMethod(groups = "fast")
+    public void setUp() throws Exception {
+        super.setUp();
+
+        final OSGIKillbillDataSource osgiKillbillDataSource = Mockito.mock(OSGIKillbillDataSource.class);
+
+        final DataSource dataSource = Mockito.mock(DataSource.class);
+        Mockito.when(osgiKillbillDataSource.getDataSource()).thenReturn(dataSource);
+
+        final OSGIKillbillLogService osgiKillbillLogService = Mockito.mock(OSGIKillbillLogService.class);
+        Mockito.doAnswer(new Answer() {
+            @Override
+            public Object answer(final InvocationOnMock invocation) throws Throwable {
+                logger.info(Arrays.toString(invocation.getArguments()));
+                return null;
+            }
+        }).when(osgiKillbillLogService).log(Mockito.anyInt(), Mockito.anyString());
+
+        invoiceDao = new BusinessInvoiceDao(osgiKillbillLogService, null, osgiKillbillDataSource, null);
+    }
+
+    @Test(groups = "fast")
+    public void testRevenueRecognizable() throws Exception {
+        // All items but CREDIT_ADJ are recognizable by default
+        Assert.assertTrue(invoiceDao.isRevenueRecognizable(createInvoiceItem(InvoiceItemType.RECURRING)
+                                                          ));
+        Assert.assertFalse(invoiceDao.isRevenueRecognizable(createInvoiceItem(InvoiceItemType.CREDIT_ADJ)
+                                                           ));
+    }
+
+    @Test(groups = "fast")
+    public void testInvoiceAdjustment() throws Exception {
+        Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(InvoiceItemType.RECURRING),
+                                                              ImmutableList.<InvoiceItem>of()));
+        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(InvoiceItemType.REFUND_ADJ),
+                                                             ImmutableList.<InvoiceItem>of()));
+
+        final InvoiceItem creditAdj = createInvoiceItem(InvoiceItemType.CREDIT_ADJ);
+
+        // Account credit
+        Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
+                                                              ImmutableList.<InvoiceItem>of(createInvoiceItem(InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
+
+        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
+                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(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()))));
+    }
+
+    @Test(groups = "fast")
+    public void testSanitization() throws Exception {
+        // One invoice, with two repairs and an external charge
+        final UUID subscriptionId1 = UUID.randomUUID();
+        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 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 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 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 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 Collection<InvoiceItem> sanitizedInvoiceItems = invoiceDao.sanitizeInvoiceItems(ImmutableList.<InvoiceItem>of(recurring1, repair1, reparation1, recurring2, repair2, reparation2, externalCharge));
+        Assert.assertEquals(sanitizedInvoiceItems.size(), 2 + 2 + 1);
+        for (final InvoiceItem invoiceItem : sanitizedInvoiceItems) {
+            if (invoiceItem.getId().equals(recurring1.getId())) {
+                Assert.assertEquals(invoiceItem, recurring1);
+            } else if (invoiceItem.getId().equals(repair1.getId())) {
+                if (InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType()) && invoiceItem.getLinkedItemId().equals(recurring1.getId())) {
+                    Assert.assertEquals(invoiceItem.getAmount(), new BigDecimal("20").negate());
+                } else {
+                    Assert.fail("Repair item 1 shouldn't be in the sanitized elements");
+                }
+            } else if (invoiceItem.getId().equals(reparation1.getId())) {
+                Assert.fail("Reparation item 1 shouldn't be in the sanitized elements");
+            } else if (invoiceItem.getId().equals(recurring2.getId())) {
+                Assert.assertEquals(invoiceItem, recurring2);
+            } else if (invoiceItem.getId().equals(repair2.getId())) {
+                if (InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType()) && invoiceItem.getLinkedItemId().equals(recurring2.getId())) {
+                    Assert.assertEquals(invoiceItem.getAmount(), new BigDecimal("15").negate());
+                } else {
+                    Assert.fail("Repair item 2 shouldn't be in the sanitized elements");
+                }
+            } else if (invoiceItem.getId().equals(reparation2.getId())) {
+                Assert.fail("Reparation item 2 shouldn't be in the sanitized elements");
+            } else if (invoiceItem.getId().equals(externalCharge.getId())) {
+                Assert.assertEquals(invoiceItem, externalCharge);
+            } else {
+                Assert.fail("Shouldn't be in the sanitized elements: " + invoiceItem);
+            }
+        }
+    }
+
+    private InvoiceItem createInvoiceItem(final InvoiceItemType type) {
+        return createInvoiceItem(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 InvoiceItemType invoiceItemType,
+                                          final UUID subscriptionId,
+                                          final LocalDate startDate,
+                                          final LocalDate endDate,
+                                          final BigDecimal amount,
+                                          @Nullable final UUID linkedItemId) {
+        final UUID invoiceItemId = UUID.randomUUID();
+
+        final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
+        Mockito.when(invoiceItem.getId()).thenReturn(invoiceItemId);
+        Mockito.when(invoiceItem.getInvoiceItemType()).thenReturn(invoiceItemType);
+        Mockito.when(invoiceItem.getInvoiceId()).thenReturn(invoiceId);
+        Mockito.when(invoiceItem.getAccountId()).thenReturn(accountId);
+        Mockito.when(invoiceItem.getStartDate()).thenReturn(startDate);
+        Mockito.when(invoiceItem.getEndDate()).thenReturn(endDate);
+        Mockito.when(invoiceItem.getAmount()).thenReturn(amount);
+        Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.EUR);
+        Mockito.when(invoiceItem.getDescription()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(invoiceItem.getBundleId()).thenReturn(bundleId);
+        Mockito.when(invoiceItem.getSubscriptionId()).thenReturn(subscriptionId);
+        Mockito.when(invoiceItem.getPlanName()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(invoiceItem.getPhaseName()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(invoiceItem.getRate()).thenReturn(new BigDecimal("1203"));
+        Mockito.when(invoiceItem.getLinkedItemId()).thenReturn(linkedItemId);
+        Mockito.when(invoiceItem.getCreatedDate()).thenReturn(new DateTime(2016, 1, 22, 10, 56, 51, DateTimeZone.UTC));
+
+        return invoiceItem;
+    }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index b2882cf..e132e4e 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -30,13 +30,13 @@ import org.eclipse.jetty.servlet.FilterHolder;
 import org.joda.time.LocalDate;
 import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
-import org.skife.config.SimplePropertyConfigSource;
 import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
 import com.ning.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import com.ning.billing.KillbillConfigSource;
 import com.ning.billing.account.glue.DefaultAccountModule;
 import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.api.TestApiListener;
@@ -171,7 +171,7 @@ public class TestJaxrsBase extends KillbillClient {
 
         @Override
         protected void installKillbillModules() {
-            final ConfigSource configSource = new SimplePropertyConfigSource(System.getProperties());
+            final KillbillConfigSource configSource = new KillbillConfigSource(System.getProperties());
 
             /*
              * For a lack of getting module override working, copy all install modules from parent class...
@@ -180,6 +180,10 @@ public class TestJaxrsBase extends KillbillClient {
             Modules.override(new com.ning.billing.payment.setup.PaymentModule()).with(new PaymentMockModule());
             */
 
+            configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "url", helper.getJdbcConnectionString());
+            configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "user", DBTestingHelper.USERNAME);
+            configSource.setProperty(AnalyticsModule.ANALYTICS_DBI_CONFIG_STRING + "password", DBTestingHelper.PASSWORD);
+
             install(new GuicyKillbillTestWithEmbeddedDBModule());
 
 
diff --git a/util/src/test/java/com/ning/billing/KillbillConfigSource.java b/util/src/test/java/com/ning/billing/KillbillConfigSource.java
index cf95af2..0382a6e 100644
--- a/util/src/test/java/com/ning/billing/KillbillConfigSource.java
+++ b/util/src/test/java/com/ning/billing/KillbillConfigSource.java
@@ -27,14 +27,18 @@ public class KillbillConfigSource implements ConfigSource {
     private final Properties properties;
 
     public KillbillConfigSource() {
-        properties = new Properties(System.getProperties());
-        properties.put("user.timezone", "UTC");
+        this(System.getProperties());
+    }
+
+    public KillbillConfigSource(final Properties properties) {
+        this.properties = new Properties(properties);
+        this.properties.put("user.timezone", "UTC");
 
         // Speed up the notification queue
-        properties.put("killbill.billing.util.notificationq.sleep", "100");
+        this.properties.put("killbill.billing.util.notificationq.sleep", "100");
         // Speed up the bus
-        properties.put("killbill.billing.util.persistent.bus.sleep", "100");
-        properties.put("killbill.billing.util.persistent.bus.nbThreads", "1");
+        this.properties.put("killbill.billing.util.persistent.bus.sleep", "100");
+        this.properties.put("killbill.billing.util.persistent.bus.nbThreads", "1");
     }
 
     public String getString(final String propertyName) {