killbill-memoizeit

analytics: first pass at refactoring the business logic Extract

4/22/2013 2:53:10 PM

Changes

Details

diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java
index 2f31a3b..e54a01c 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAccountDao.java
@@ -16,24 +16,14 @@
 
 package com.ning.billing.osgi.bundles.analytics.dao;
 
-import java.math.BigDecimal;
-import java.util.Collection;
-import java.util.List;
 import java.util.UUID;
 
 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.ProductCategory;
-import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessAccountFactory;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
-import com.ning.billing.payment.api.Payment;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
@@ -41,17 +31,18 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessAccountDao extends BusinessAnalyticsDaoBase {
 
+    private final BusinessAccountFactory bacFactory;
+
     public BusinessAccountDao(final OSGIKillbillLogService logService,
                               final OSGIKillbillAPI osgiKillbillAPI,
                               final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
+        bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        final Account account = getAccount(accountId, context);
-
         // Recompute the account record
-        final BusinessAccountModelDao bac = createBusinessAccount(account, context);
+        final BusinessAccountModelDao bac = bacFactory.createBusinessAccount(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
             @Override
@@ -63,62 +54,10 @@ public class BusinessAccountDao extends BusinessAnalyticsDaoBase {
     }
 
     // Note: computing the BusinessAccountModelDao object is fairly expensive, hence should be done outside of the transaction
-    public void updateInTransaction(final BusinessAccountModelDao bac, final BusinessAnalyticsSqlDao transactional, final CallContext context) {
+    public void updateInTransaction(final BusinessAccountModelDao bac,
+                                    final BusinessAnalyticsSqlDao transactional,
+                                    final CallContext context) {
         transactional.deleteByAccountRecordId(bac.getTableName(), bac.getAccountRecordId(), bac.getTenantRecordId(), context);
         transactional.create(bac.getTableName(), bac, context);
     }
-
-    public BusinessAccountModelDao createBusinessAccount(final Account account, final CallContext context) throws AnalyticsRefreshException {
-        // Retrieve the account creation audit log
-        final AuditLog creationAuditLog = getAccountCreationAuditLog(account.getId(), context);
-
-        // Retrieve the account balance
-        // Note: since we retrieve the invoices below, we could compute it ourselves and avoid fetching the invoices
-        // twice, but that way the computation logic is owned by invoice
-        final BigDecimal accountBalance = getAccountBalance(account.getId(), context);
-
-        // Retrieve invoices information
-        Invoice lastInvoice = null;
-        final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
-        for (final Invoice invoice : invoices) {
-            if (lastInvoice == null || invoice.getInvoiceDate().isAfter(lastInvoice.getInvoiceDate())) {
-                lastInvoice = invoice;
-            }
-        }
-
-        // Retrieve payments information
-        Payment lastPayment = null;
-        final Collection<Payment> payments = getPaymentsByAccountId(account.getId(), context);
-        for (final Payment payment : payments) {
-            if (lastPayment == null || payment.getEffectiveDate().isAfter(lastPayment.getEffectiveDate())) {
-                lastPayment = payment;
-            }
-        }
-
-        final List<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(account.getId(), context);
-        int nbActiveBundles = 0;
-        for (final SubscriptionBundle bundle : bundles) {
-            final Collection<Subscription> subscriptionsForBundle = getSubscriptionsForBundle(bundle.getId(), context);
-            for (final Subscription subscription : subscriptionsForBundle) {
-                if (ProductCategory.BASE.equals(subscription.getCategory()) &&
-                    !(subscription.getEndDate() != null && !subscription.getEndDate().isAfterNow())) {
-                    nbActiveBundles++;
-                }
-            }
-        }
-
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
-        return new BusinessAccountModelDao(account,
-                                           accountRecordId,
-                                           accountBalance,
-                                           lastInvoice,
-                                           lastPayment,
-                                           nbActiveBundles,
-                                           creationAuditLog,
-                                           tenantRecordId,
-                                           reportGroup);
-    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java
index 9395500..f676281 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsDaoBase.java
@@ -18,20 +18,13 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 
 import org.skife.jdbi.v2.DBI;
 
-import com.ning.billing.osgi.bundles.analytics.BusinessAnalyticsBase;
-import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
-import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
-public class BusinessAnalyticsDaoBase extends BusinessAnalyticsBase {
+public class BusinessAnalyticsDaoBase {
 
     protected final BusinessAnalyticsSqlDao sqlDao;
 
-    public BusinessAnalyticsDaoBase(final OSGIKillbillLogService logService,
-                                    final OSGIKillbillAPI osgiKillbillAPI,
-                                    final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI);
-
+    public BusinessAnalyticsDaoBase(final OSGIKillbillDataSource osgiKillbillDataSource) {
         final DBI dbi = BusinessDBIProvider.get(osgiKillbillDataSource.getDataSource());
         sqlDao = dbi.onDemand(BusinessAnalyticsSqlDao.class);
     }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java
index e49a686..75b5bdc 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessBundleSummaryDao.java
@@ -17,36 +17,19 @@
 package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.util.Collection;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.UUID;
 
-import com.ning.billing.account.api.Account;
-import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscriptionTransitionModelDao;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
 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.Iterables;
-import com.google.common.collect.Ordering;
-
 public class BusinessBundleSummaryDao extends BusinessAnalyticsDaoBase {
 
     public BusinessBundleSummaryDao(final OSGIKillbillLogService logService,
                                     final OSGIKillbillAPI osgiKillbillAPI,
                                     final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
     }
 
     public void updateInTransaction(final Collection<BusinessBundleSummaryModelDao> bbss,
@@ -65,76 +48,4 @@ public class BusinessBundleSummaryDao extends BusinessAnalyticsDaoBase {
 
         // The update of summary columns in BAC will be done via BST
     }
-
-    public Collection<BusinessBundleSummaryModelDao> createBusinessBundleSummaries(final Account account,
-                                                                                   final Long accountRecordId,
-                                                                                   final Collection<BusinessSubscriptionTransitionModelDao> bsts,
-                                                                                   final Long tenantRecordId,
-                                                                                   final ReportGroup reportGroup,
-                                                                                   final CallContext context) throws AnalyticsRefreshException {
-        final Map<UUID, Integer> rankForBundle = new LinkedHashMap<UUID, Integer>();
-        final Map<UUID, BusinessSubscriptionTransitionModelDao> bstForBundle = new LinkedHashMap<UUID, BusinessSubscriptionTransitionModelDao>();
-        filterBstsForBasePlans(bsts, rankForBundle, bstForBundle);
-
-        final Collection<BusinessBundleSummaryModelDao> bbss = new LinkedList<BusinessBundleSummaryModelDao>();
-        for (final BusinessSubscriptionTransitionModelDao bst : bstForBundle.values()) {
-            final BusinessBundleSummaryModelDao bbs = buildBBS(account,
-                                                               accountRecordId,
-                                                               bst,
-                                                               rankForBundle.get(bst.getBundleId()),
-                                                               tenantRecordId,
-                                                               reportGroup,
-                                                               context);
-            bbss.add(bbs);
-        }
-        return bbss;
-    }
-
-    @VisibleForTesting
-    void filterBstsForBasePlans(final Collection<BusinessSubscriptionTransitionModelDao> bsts, final Map<UUID, Integer> rankForBundle, final Map<UUID, BusinessSubscriptionTransitionModelDao> bstForBundle) {// Find bsts for BASE subscriptions only and sort them using the next start date
-        final Collection<BusinessSubscriptionTransitionModelDao> sortedBundlesBst = Ordering.from(new Comparator<BusinessSubscriptionTransitionModelDao>() {
-            @Override
-            public int compare(final BusinessSubscriptionTransitionModelDao o1, final BusinessSubscriptionTransitionModelDao o2) {
-                return o1.getNextStartDate().compareTo(o2.getNextStartDate());
-            }
-        }).sortedCopy(Iterables.filter(bsts, new Predicate<BusinessSubscriptionTransitionModelDao>() {
-            @Override
-            public boolean apply(final BusinessSubscriptionTransitionModelDao input) {
-                return ProductCategory.BASE.toString().equals(input.getNextProductCategory());
-            }
-        }));
-
-        UUID lastBundleId = null;
-        Integer lastBundleRank = 0;
-        for (final BusinessSubscriptionTransitionModelDao bst : sortedBundlesBst) {
-            // Note that sortedBundlesBst is not ordered bundle by bundle, i.e. we may have:
-            // bundleId1 CREATE, bundleId2 CREATE, bundleId1 PHASE, bundleId3 CREATE bundleId2 PHASE
-            if (lastBundleId == null || (!lastBundleId.equals(bst.getBundleId()) && rankForBundle.get(bst.getBundleId()) == null)) {
-                lastBundleRank++;
-                lastBundleId = bst.getBundleId();
-                rankForBundle.put(lastBundleId, lastBundleRank);
-            }
-
-            if (bstForBundle.get(bst.getBundleId()) == null ||
-                bstForBundle.get(bst.getBundleId()).getNextStartDate().isBefore(bst.getNextStartDate())) {
-                bstForBundle.put(bst.getBundleId(), bst);
-            }
-        }
-    }
-
-    private BusinessBundleSummaryModelDao buildBBS(final Account account, final Long accountRecordId, final BusinessSubscriptionTransitionModelDao bst, final Integer bundleAccountRank, final Long tenantRecordId, final ReportGroup reportGroup, final CallContext context) throws AnalyticsRefreshException {
-        final SubscriptionBundle bundle = getSubscriptionBundle(bst.getBundleId(), context);
-        final Long bundleRecordId = getBundleRecordId(bundle.getId(), context);
-        final AuditLog creationAuditLog = getBundleCreationAuditLog(bundle.getId(), context);
-
-        return new BusinessBundleSummaryModelDao(account,
-                                                 accountRecordId,
-                                                 bundle,
-                                                 bundleRecordId,
-                                                 bundleAccountRank,
-                                                 bst,
-                                                 creationAuditLog,
-                                                 tenantRecordId,
-                                                 reportGroup);
-    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java
index d1b330e..c205a88 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessFieldDao.java
@@ -17,35 +17,32 @@
 package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.util.Collection;
-import java.util.LinkedList;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
-import com.ning.billing.account.api.Account;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessFieldFactory;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessFieldModelDao;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.customfield.CustomField;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessFieldDao extends BusinessAnalyticsDaoBase {
 
+    private final BusinessFieldFactory bFieldFactory;
+
     public BusinessFieldDao(final OSGIKillbillLogService logService,
                             final OSGIKillbillAPI osgiKillbillAPI,
                             final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
+        bFieldFactory = new BusinessFieldFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        final Account account = getAccount(accountId, context);
-
-        final Collection<BusinessFieldModelDao> fieldModelDaos = createBusinessFields(account, context);
+        final Collection<BusinessFieldModelDao> fieldModelDaos = bFieldFactory.createBusinessFields(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
             @Override
@@ -69,28 +66,4 @@ public class BusinessFieldDao extends BusinessAnalyticsDaoBase {
             transactional.create(fieldModelDao.getTableName(), fieldModelDao, context);
         }
     }
-
-    private Collection<BusinessFieldModelDao> createBusinessFields(final Account account, final CallContext context) throws AnalyticsRefreshException {
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
-        final Collection<CustomField> fields = getFieldsForAccount(account.getId(), context);
-
-        final Collection<BusinessFieldModelDao> fieldModelDaos = new LinkedList<BusinessFieldModelDao>();
-        for (final CustomField field : fields) {
-            final Long customFieldRecordId = getFieldRecordId(field.getId(), context);
-            final AuditLog creationAuditLog = getFieldCreationAuditLog(field.getId(), context);
-            final BusinessFieldModelDao fieldModelDao = BusinessFieldModelDao.create(account,
-                                                                                     accountRecordId,
-                                                                                     field,
-                                                                                     customFieldRecordId,
-                                                                                     creationAuditLog,
-                                                                                     tenantRecordId,
-                                                                                     reportGroup);
-            fieldModelDaos.add(fieldModelDao);
-        }
-
-        return fieldModelDaos;
-    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
index d41d613..5a77d36 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
@@ -26,8 +26,10 @@ import java.util.UUID;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
-import com.ning.billing.account.api.Account;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessAccountFactory;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessInvoiceFactory;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessInvoicePaymentFactory;
 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.BusinessInvoiceModelDao;
@@ -39,40 +41,47 @@ 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.collect.Iterables;
 
 /**
  * Wrapper around BusinessInvoiceDao and BusinessInvoicePaymentDao.
  * <p/>
- * These two should always be updated together as invoice and payment information is denormalized across tables.
+ * These two should always be updated together as invoice and payment information is denormalized across
+ * bot sets of tables.
  */
 public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBase {
 
     private final BusinessAccountDao businessAccountDao;
     private final BusinessInvoiceDao businessInvoiceDao;
     private final BusinessInvoicePaymentDao businessInvoicePaymentDao;
+    private final BusinessAccountFactory bacFactory;
+    private final BusinessInvoiceFactory binFactory;
+    private final BusinessInvoicePaymentFactory bipFactory;
 
     public BusinessInvoiceAndInvoicePaymentDao(final OSGIKillbillLogService logService,
                                                final OSGIKillbillAPI osgiKillbillAPI,
                                                final OSGIKillbillDataSource osgiKillbillDataSource,
                                                final BusinessAccountDao businessAccountDao) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
         this.businessAccountDao = businessAccountDao;
-        this.businessInvoiceDao = new BusinessInvoiceDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
-        this.businessInvoicePaymentDao = new BusinessInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.businessInvoiceDao = new BusinessInvoiceDao(osgiKillbillDataSource);
+        this.businessInvoicePaymentDao = new BusinessInvoicePaymentDao(osgiKillbillDataSource);
+        bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
+        binFactory = new BusinessInvoiceFactory(logService, osgiKillbillAPI);
+        bipFactory = new BusinessInvoicePaymentFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        final Account account = getAccount(accountId, context);
-
         // Recompute the account record
-        final BusinessAccountModelDao bac = businessAccountDao.createBusinessAccount(account, context);
+        final BusinessAccountModelDao bac = bacFactory.createBusinessAccount(accountId, context);
 
+        // Recompute invoice, invoice items and invoice payments records
         final Map<UUID, BusinessInvoiceModelDao> invoices = new HashMap<UUID, BusinessInvoiceModelDao>();
         final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
         final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments = new HashMap<UUID, Collection<BusinessInvoicePaymentBaseModelDao>>();
-        createBusinessPojos(account, invoices, invoiceItems, invoicePayments, context);
+        createBusinessPojos(accountId, invoices, invoiceItems, invoicePayments, context);
 
-        // Delete and recreate invoice and invoice items in the transaction
+        // Delete and recreate all items in the transaction
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
             @Override
             public Void inTransaction(final BusinessAnalyticsSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -83,17 +92,17 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
     }
 
     @VisibleForTesting
-    void createBusinessPojos(final Account account,
+    void createBusinessPojos(final UUID accountId,
                              final Map<UUID, BusinessInvoiceModelDao> invoices,
                              final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems,
                              final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments,
                              final CallContext context) throws AnalyticsRefreshException {
         // Recompute all invoices and invoice items. Invoices will have their denormalized payment fields missing,
         // and items won't have neither invoice nor payment denormalized fields populated
-        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices = businessInvoiceDao.createBusinessInvoicesAndInvoiceItems(account, context);
+        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices = binFactory.createBusinessInvoicesAndInvoiceItems(accountId, context);
 
         // Recompute all invoice payments (without denormalized payment fields populated)
-        final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments = businessInvoicePaymentDao.createBusinessInvoicePayments(account, businessInvoices, context);
+        final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments = bipFactory.createBusinessInvoicePayments(accountId, businessInvoices, context);
 
         // Transform the results
         for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.keySet()) {
@@ -150,24 +159,29 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
         }
     }
 
+    /**
+     * Refresh the records. This does not perform any logic but simply deletes existing records and inserts the current ones.
+     *
+     * @param bac             current, fully populated, BusinessAccountModelDao record
+     * @param invoices        current, fully populated, mapping of invoice id -> BusinessInvoiceModelDao records
+     * @param invoiceItems    current, fully populated, mapping of invoice id -> BusinessInvoiceItemBaseModelDao records
+     * @param invoicePayments current, fully populated, mapping of invoice id -> BusinessInvoicePaymentBaseModelDao records
+     * @param transactional   current transaction
+     * @param context         call context
+     */
     private void updateInTransaction(final BusinessAccountModelDao bac,
                                      final Map<UUID, BusinessInvoiceModelDao> invoices,
                                      final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems,
                                      final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments,
                                      final BusinessAnalyticsSqlDao transactional,
-                                     final CallContext context) throws AnalyticsRefreshException {
-        // Update invoice tables
+                                     final CallContext context) {
+        // Update invoice and invoice items tables
         businessInvoiceDao.updateInTransaction(bac, invoices, invoiceItems, transactional, context);
 
         // Update invoice payment tables
-        // TODO flatten function?
-        final Collection<BusinessInvoicePaymentBaseModelDao> flattenedInvoicePayments = new LinkedList<BusinessInvoicePaymentBaseModelDao>();
-        for (final UUID invoiceId : invoicePayments.keySet()) {
-            flattenedInvoicePayments.addAll(invoicePayments.get(invoiceId));
-        }
-        businessInvoicePaymentDao.updateInTransaction(bac, flattenedInvoicePayments, transactional, context);
+        businessInvoicePaymentDao.updateInTransaction(bac, Iterables.<BusinessInvoicePaymentBaseModelDao>concat(invoicePayments.values()), transactional, context);
 
-        // Update invoice and payment details in BAC
+        // Update denormalized invoice and payment details in BAC
         businessAccountDao.updateInTransaction(bac, transactional, context);
     }
 }
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 ee396ab..fe3f936 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,78 +16,37 @@
 
 package com.ning.billing.osgi.bundles.analytics.dao;
 
-import java.math.BigDecimal;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.Map;
-import java.util.Set;
 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 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.osgi.bundles.analytics.utils.BusinessInvoiceUtils;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.TenantContext;
-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.base.Strings;
-import com.google.common.collect.Collections2;
-
-import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isAccountCreditItem;
-import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isCharge;
-import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isInvoiceAdjustmentItem;
-import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isInvoiceItemAdjustmentItem;
-import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isRevenueRecognizable;
 
 public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
 
-    public BusinessInvoiceDao(final OSGIKillbillLogService logService,
-                              final OSGIKillbillAPI osgiKillbillAPI,
-                              final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+    public BusinessInvoiceDao(final OSGIKillbillDataSource osgiKillbillDataSource) {
+        super(osgiKillbillDataSource);
     }
 
+    /**
+     * Delete all invoice and invoice item records and insert the specified ones as current.
+     *
+     * @param bac                  current, fully populated, BusinessAccountModelDao record
+     * @param businessInvoices     current, fully populated, mapping of invoice id -> BusinessInvoiceModelDao records
+     * @param businessInvoiceItems current, fully populated, mapping of invoice id -> BusinessInvoiceItemBaseModelDao records
+     * @param transactional        current transaction
+     * @param context              call context
+     */
     public void updateInTransaction(final BusinessAccountModelDao bac,
                                     final Map<UUID, BusinessInvoiceModelDao> businessInvoices,
                                     final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems,
                                     final BusinessAnalyticsSqlDao transactional,
-                                    final CallContext context) throws AnalyticsRefreshException {
-        rebuildInvoicesForAccountInTransaction(bac, businessInvoices, businessInvoiceItems, transactional, context);
-        // Invoice and payment details in BAC will be updated by BusinessInvoiceAndInvoicePaymentDao
-    }
-
-    private void rebuildInvoicesForAccountInTransaction(final BusinessAccountModelDao account,
-                                                        final Map<UUID, BusinessInvoiceModelDao> businessInvoices,
-                                                        final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems,
-                                                        final BusinessAnalyticsSqlDao transactional,
-                                                        final CallContext context) {
-        deleteInvoicesAndInvoiceItemsForAccountInTransaction(transactional, account.getAccountRecordId(), account.getTenantRecordId(), context);
+                                    final CallContext context) {
+        deleteInvoicesAndInvoiceItemsForAccountInTransaction(transactional, bac.getAccountRecordId(), bac.getTenantRecordId(), context);
 
         for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.values()) {
             final Collection<BusinessInvoiceItemBaseModelDao> invoiceItems = businessInvoiceItems.get(businessInvoice.getInvoiceId());
@@ -95,6 +54,8 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                 createInvoiceInTransaction(transactional, businessInvoice, invoiceItems, context);
             }
         }
+
+        // Invoice and payment details in BAC will be updated by BusinessInvoiceAndInvoicePaymentDao
     }
 
     private void deleteInvoicesAndInvoiceItemsForAccountInTransaction(final BusinessAnalyticsSqlDao transactional,
@@ -122,522 +83,4 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
             transactional.create(invoiceItem.getTableName(), invoiceItem, context);
         }
     }
-
-    /**
-     * Create business invoices and invoice items to record. Note that these POJOs are incomplete
-     * (denormalized payment fields have not yet been populated)
-     *
-     * @param account current account refreshed
-     * @param context call context
-     * @return all business invoice and invoice items to create
-     * @throws AnalyticsRefreshException
-     */
-    public Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> createBusinessInvoicesAndInvoiceItems(final Account account,
-                                                                                                                           final CallContext context) throws AnalyticsRefreshException {
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
-        // Lookup the invoices for that account
-        final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
-
-        // All invoice items across all invoices for that accounr (we need to be able to reference items across multiple invoices)
-        final Collection<InvoiceItem> allInvoiceItems = new LinkedList<InvoiceItem>();
-        // Convenient mapping invoice_id -> invoice
-        final Map<UUID, Invoice> invoiceIdToInvoiceMappings = new LinkedHashMap<UUID, Invoice>();
-        for (final Invoice invoice : invoices) {
-            invoiceIdToInvoiceMappings.put(invoice.getId(), invoice);
-            allInvoiceItems.addAll(invoice.getInvoiceItems());
-        }
-
-        // Sanitize (cherry-pick, merge) the items
-        final Collection<InvoiceItem> sanitizedInvoiceItems = sanitizeInvoiceItems(allInvoiceItems);
-
-        // Create the business invoice items. These are incomplete (the denormalized invoice fields haven't been computed yet)
-        final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItemsForInvoiceId = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
-        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 (businessInvoiceItemsForInvoiceId.get(invoice.getId()) == null) {
-                    businessInvoiceItemsForInvoiceId.put(invoice.getId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
-                }
-                businessInvoiceItemsForInvoiceId.get(invoice.getId()).add(businessInvoiceItem);
-            }
-        }
-
-        // Now, create the business invoices
-        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessRecords = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>>();
-        for (final Invoice invoice : invoices) {
-            final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems = businessInvoiceItemsForInvoiceId.get(invoice.getId());
-            if (businessInvoiceItems == null) {
-                continue;
-            }
-
-            final BusinessInvoiceModelDao businessInvoice = createBusinessInvoice(account, invoice, businessInvoiceItems, accountRecordId, tenantRecordId, reportGroup, context);
-            businessRecords.put(businessInvoice, businessInvoiceItems);
-        }
-
-        return businessRecords;
-    }
-
-    private BusinessInvoiceModelDao createBusinessInvoice(final Account account,
-                                                          final Invoice invoice,
-                                                          final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
-                                                          final Long accountRecordId,
-                                                          final Long tenantRecordId,
-                                                          @Nullable final ReportGroup reportGroup,
-                                                          final CallContext context) throws AnalyticsRefreshException {
-        final Long invoiceRecordId = getInvoiceRecordId(invoice.getId(), context);
-        final AuditLog creationAuditLog = getInvoiceCreationAuditLog(invoice.getId(), context);
-
-        final BigDecimal amountCharged = BusinessInvoiceUtils.computeInvoiceAmountCharged(businessInvoiceItems);
-        final BigDecimal originalAmountCharged = BusinessInvoiceUtils.computeInvoiceOriginalAmountCharged(businessInvoiceItems);
-        final BigDecimal amountCredited = BusinessInvoiceUtils.computeInvoiceAmountCredited(businessInvoiceItems);
-
-        return new BusinessInvoiceModelDao(account,
-                                           accountRecordId,
-                                           invoice,
-                                           amountCharged,
-                                           originalAmountCharged,
-                                           amountCredited,
-                                           invoiceRecordId,
-                                           creationAuditLog,
-                                           tenantRecordId,
-                                           reportGroup);
-    }
-
-    private BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
-                                                                      final Invoice invoice,
-                                                                      final InvoiceItem invoiceItem,
-                                                                      final Collection<InvoiceItem> otherInvoiceItems,
-                                                                      final Long accountRecordId,
-                                                                      final Long tenantRecordId,
-                                                                      @Nullable final ReportGroup reportGroup,
-                                                                      final TenantContext context) throws AnalyticsRefreshException {
-        SubscriptionBundle bundle = null;
-        // Subscription and bundle could be null for e.g. credits or adjustments
-        if (invoiceItem.getBundleId() != null) {
-            bundle = getSubscriptionBundle(invoiceItem.getBundleId(), context);
-        }
-
-        Plan plan = null;
-        if (Strings.emptyToNull(invoiceItem.getPlanName()) != null) {
-            plan = getPlanFromInvoiceItem(invoiceItem, context);
-        }
-
-        PlanPhase planPhase = null;
-        if (invoiceItem.getSubscriptionId() != null && Strings.emptyToNull(invoiceItem.getPhaseName()) != null) {
-            planPhase = getPlanPhaseFromInvoiceItem(invoiceItem, context);
-        }
-
-        final Long invoiceItemRecordId = getInvoiceItemRecordId(invoiceItem.getId(), context);
-        final AuditLog creationAuditLog = getInvoiceItemCreationAuditLog(invoiceItem.getId(), context);
-
-        return createBusinessInvoiceItem(account,
-                                         invoice,
-                                         invoiceItem,
-                                         otherInvoiceItems,
-                                         bundle,
-                                         plan,
-                                         planPhase,
-                                         invoiceItemRecordId,
-                                         creationAuditLog,
-                                         accountRecordId,
-                                         tenantRecordId,
-                                         reportGroup,
-                                         context);
-    }
-
-    @VisibleForTesting
-    BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
-                                                              final Invoice invoice,
-                                                              final InvoiceItem invoiceItem,
-                                                              final Collection<InvoiceItem> otherInvoiceItems,
-                                                              @Nullable final SubscriptionBundle bundle,
-                                                              @Nullable final Plan plan,
-                                                              @Nullable final PlanPhase planPhase,
-                                                              final Long invoiceItemRecordId,
-                                                              final AuditLog creationAuditLog,
-                                                              final Long accountRecordId,
-                                                              final Long tenantRecordId,
-                                                              final ReportGroup reportGroup,
-                                                              final TenantContext context) throws AnalyticsRefreshException {
-        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, otherInvoiceItems)) {
-            businessInvoiceItemType = BusinessInvoiceItemType.INVOICE_ADJUSTMENT;
-        } else {
-            // We don't care
-            return null;
-        }
-
-        final Boolean revenueRecognizable = isRevenueRecognizable(invoiceItem, otherInvoiceItems);
-
-        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,
-                                                      plan,
-                                                      planPhase,
-                                                      creationAuditLog,
-                                                      tenantRecordId,
-                                                      reportGroup);
-    }
-
-    /**
-     * Filter and transform the original invoice items for Analytics purposes. We mainly
-     * merge REPAIR_ADJ items with reparation items (reparees) to create item adjustments.
-     *
-     * @param allInvoiceItems all items for the current account
-     * @return invoice items interesting for Analytics purposes
-     */
-    @VisibleForTesting
-    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) {
-            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 : allInvoiceItems) {
-            // Skip non-repaired items
-            if (!repairedInvoiceItemIdToRepairInvoiceItemMappings.keySet().contains(repairedInvoiceItem.getId())) {
-                continue;
-            }
-
-            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 &&
-                    // FIXED items have a null end date
-                    ((repairedInvoiceItem.getEndDate() == null && invoiceItem.getEndDate() == null) ||
-                     (repairedInvoiceItem.getEndDate() != null && invoiceItem.getEndDate() != null && !repairedInvoiceItem.getEndDate().isBefore(invoiceItem.getEndDate()))) &&
-                    !repairedInvoiceItem.getId().equals(invoiceItem.getId())) {
-                    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!");
-            }
-        }
-
-        // We now need to adjust the CBA_ADJ for the repair items
-        final Set<UUID> cbasToIgnore = new HashSet<UUID>();
-        final Collection<AdjustedCBAInvoiceItem> newCbasToAdd = new LinkedList<AdjustedCBAInvoiceItem>();
-        for (final InvoiceItem cbaInvoiceItem : allInvoiceItems) {
-            if (!InvoiceItemType.CBA_ADJ.equals(cbaInvoiceItem.getInvoiceItemType())) {
-                continue;
-            }
-
-            for (final InvoiceItem invoiceItem : allInvoiceItems) {
-                if (reparationInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
-                    final InvoiceItem repairInvoiceItem = reparationInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
-                    final InvoiceItem reparationInvoiceItem = invoiceItem;
-                    // Au petit bonheur la chance... There is nothing else against to compare
-                    if (repairInvoiceItem.getAmount().negate().compareTo(cbaInvoiceItem.getAmount()) == 0) {
-                        cbasToIgnore.add(cbaInvoiceItem.getId());
-                        newCbasToAdd.add(new AdjustedCBAInvoiceItem(cbaInvoiceItem, cbaInvoiceItem.getAmount().add(reparationInvoiceItem.getAmount().negate()), reparationInvoiceItem.getId()));
-
-                        // Now, fiddle with the CBA used on the reparation invoice
-                        for (final InvoiceItem cbaUsedOnNextInvoiceItem : allInvoiceItems) {
-                            if (!InvoiceItemType.CBA_ADJ.equals(cbaUsedOnNextInvoiceItem.getInvoiceItemType()) ||
-                                !cbaUsedOnNextInvoiceItem.getInvoiceId().equals(reparationInvoiceItem.getInvoiceId())) {
-                                continue;
-                            }
-
-                            // Au petit bonheur la chance... There is nothing else against to compare. Take the first one again?
-                            cbasToIgnore.add(cbaUsedOnNextInvoiceItem.getId());
-                            newCbasToAdd.add(new AdjustedCBAInvoiceItem(cbaUsedOnNextInvoiceItem, cbaUsedOnNextInvoiceItem.getAmount().add(reparationInvoiceItem.getAmount()), reparationInvoiceItem.getId()));
-                            break;
-                        }
-
-                        // Break from the inner loop only
-                        break;
-                    }
-                }
-            }
-        }
-
-
-        // Filter the invoice items for analytics
-        final Collection<InvoiceItem> invoiceItemsForAnalytics = new LinkedList<InvoiceItem>();
-        for (final InvoiceItem invoiceItem : allInvoiceItems) {
-            if (cbasToIgnore.contains(invoiceItem.getId())) {
-                // We don't care
-            } else 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);
-            }
-        }
-        invoiceItemsForAnalytics.addAll(newCbasToAdd);
-
-        return invoiceItemsForAnalytics;
-    }
-
-    private class AdjustedCBAInvoiceItem implements InvoiceItem {
-
-        private final InvoiceItem cbaInvoiceItem;
-        private final BigDecimal amount;
-        private final UUID reparationItemId;
-
-        private AdjustedCBAInvoiceItem(final InvoiceItem cbaInvoiceItem,
-                                       final BigDecimal amount,
-                                       final UUID reparationItemId) {
-            this.cbaInvoiceItem = cbaInvoiceItem;
-            this.amount = amount;
-            this.reparationItemId = reparationItemId;
-        }
-
-        @Override
-        public InvoiceItemType getInvoiceItemType() {
-            return InvoiceItemType.CBA_ADJ;
-        }
-
-        @Override
-        public UUID getInvoiceId() {
-            return cbaInvoiceItem.getInvoiceId();
-        }
-
-        @Override
-        public UUID getAccountId() {
-            return cbaInvoiceItem.getAccountId();
-        }
-
-        @Override
-        public LocalDate getStartDate() {
-            return cbaInvoiceItem.getStartDate();
-        }
-
-        @Override
-        public LocalDate getEndDate() {
-            return cbaInvoiceItem.getStartDate();
-        }
-
-        @Override
-        public BigDecimal getAmount() {
-            return amount;
-        }
-
-        @Override
-        public Currency getCurrency() {
-            return cbaInvoiceItem.getCurrency();
-        }
-
-        @Override
-        public String getDescription() {
-            return cbaInvoiceItem.getDescription();
-        }
-
-        @Override
-        public UUID getBundleId() {
-            return cbaInvoiceItem.getBundleId();
-        }
-
-        @Override
-        public UUID getSubscriptionId() {
-            return cbaInvoiceItem.getSubscriptionId();
-        }
-
-        @Override
-        public String getPlanName() {
-            return cbaInvoiceItem.getPlanName();
-        }
-
-        @Override
-        public String getPhaseName() {
-            return cbaInvoiceItem.getPhaseName();
-        }
-
-        @Override
-        public BigDecimal getRate() {
-            return cbaInvoiceItem.getRate();
-        }
-
-        @Override
-        public UUID getLinkedItemId() {
-            return cbaInvoiceItem.getLinkedItemId();
-        }
-
-        @Override
-        public boolean matches(final Object other) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public UUID getId() {
-            return cbaInvoiceItem.getId();
-        }
-
-        public UUID getSecondId() {
-            return reparationItemId;
-        }
-
-        @Override
-        public DateTime getCreatedDate() {
-            return cbaInvoiceItem.getCreatedDate();
-        }
-
-        @Override
-        public DateTime getUpdatedDate() {
-            return cbaInvoiceItem.getUpdatedDate();
-        }
-    }
-
-    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 boolean matches(final Object other) {
-            throw new UnsupportedOperationException();
-        }
-
-        @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/BusinessInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java
index 423024c..a3b4276 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoicePaymentDao.java
@@ -16,50 +16,29 @@
 
 package com.ning.billing.osgi.bundles.analytics.dao;
 
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-
-import com.ning.billing.account.api.Account;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoicePayment;
-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.BusinessInvoiceModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentBaseModelDao;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
-import com.ning.billing.payment.api.Payment;
-import com.ning.billing.payment.api.PaymentMethod;
-import com.ning.billing.payment.api.Refund;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
-import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
 
-    public BusinessInvoicePaymentDao(final OSGIKillbillLogService logService,
-                                     final OSGIKillbillAPI osgiKillbillAPI,
-                                     final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+    public BusinessInvoicePaymentDao(final OSGIKillbillDataSource osgiKillbillDataSource) {
+        super(osgiKillbillDataSource);
     }
 
+    /**
+     * Delete all invoice payment records and insert the specified ones as current.
+     *
+     * @param bac                     current, fully populated, BusinessAccountModelDao record
+     * @param businessInvoicePayments current, fully populated, mapping of invoice id -> BusinessInvoicePaymentBaseModelDao records
+     * @param transactional           current transaction
+     * @param context                 call context
+     */
     public void updateInTransaction(final BusinessAccountModelDao bac,
-                                    final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments,
+                                    final Iterable<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments,
                                     final BusinessAnalyticsSqlDao transactional,
-                                    final CallContext context) throws AnalyticsRefreshException {
-        rebuildInvoicePaymentsForAccountInTransaction(bac, businessInvoicePayments, transactional, context);
-        // Invoice and payment details in BAC will be updated by BusinessInvoiceAndInvoicePaymentDao
-    }
-
-    private void rebuildInvoicePaymentsForAccountInTransaction(final BusinessAccountModelDao bac,
-                                                               final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments,
-                                                               final BusinessAnalyticsSqlDao transactional,
-                                                               final CallContext context) throws AnalyticsRefreshException {
+                                    final CallContext context) {
         for (final String tableName : BusinessInvoicePaymentBaseModelDao.ALL_INVOICE_PAYMENTS_TABLE_NAMES) {
             transactional.deleteByAccountRecordId(tableName, bac.getAccountRecordId(), bac.getTenantRecordId(), context);
         }
@@ -67,63 +46,7 @@ public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
         for (final BusinessInvoicePaymentBaseModelDao invoicePayment : businessInvoicePayments) {
             transactional.create(invoicePayment.getTableName(), invoicePayment, context);
         }
-    }
-
-    public Collection<BusinessInvoicePaymentBaseModelDao> createBusinessInvoicePayments(final Account account,
-                                                                                        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
-                                                                                        final CallContext context) throws AnalyticsRefreshException {
-        final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments = new LinkedList<BusinessInvoicePaymentBaseModelDao>();
 
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
-        final Collection<InvoicePayment> invoicePayments = getAccountInvoicePayments(account.getId(), context);
-        for (final InvoicePayment invoicePayment : invoicePayments) {
-            final BusinessInvoicePaymentBaseModelDao businessInvoicePayment = createBusinessInvoicePayment(account,
-                                                                                                           invoicePayment,
-                                                                                                           businessInvoices,
-                                                                                                           accountRecordId,
-                                                                                                           tenantRecordId,
-                                                                                                           reportGroup,
-                                                                                                           context);
-            if (businessInvoicePayment != null) {
-                businessInvoicePayments.add(businessInvoicePayment);
-            }
-        }
-
-        return businessInvoicePayments;
-    }
-
-    private BusinessInvoicePaymentBaseModelDao createBusinessInvoicePayment(final Account account,
-                                                                            final InvoicePayment invoicePayment,
-                                                                            final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
-                                                                            final Long accountRecordId,
-                                                                            final Long tenantRecordId,
-                                                                            @Nullable final ReportGroup reportGroup,
-                                                                            final CallContext context) throws AnalyticsRefreshException {
-        final Long invoicePaymentRecordId = getInvoicePaymentRecordId(invoicePayment.getId(), context);
-
-        final Payment payment = getPaymentWithPluginInfo(invoicePayment.getPaymentId(), context);
-        Refund refund = null;
-        if (invoicePayment.getPaymentCookieId() != null) {
-            refund = getRefundWithPluginInfo(invoicePayment.getPaymentCookieId(), context);
-        }
-
-        final Invoice invoice = getInvoice(invoicePayment.getInvoiceId(), context);
-        final PaymentMethod paymentMethod = getPaymentMethod(payment.getPaymentMethodId(), context);
-        final AuditLog creationAuditLog = getInvoicePaymentCreationAuditLog(invoicePayment.getId(), context);
-
-        return BusinessInvoicePaymentBaseModelDao.create(account,
-                                                         accountRecordId,
-                                                         invoice,
-                                                         invoicePayment,
-                                                         invoicePaymentRecordId,
-                                                         payment,
-                                                         refund,
-                                                         paymentMethod,
-                                                         creationAuditLog,
-                                                         tenantRecordId,
-                                                         reportGroup);
+        // Invoice and payment details in BAC will be updated by BusinessInvoiceAndInvoicePaymentDao
     }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java
index 9812140..27352ed 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessOverdueStatusDao.java
@@ -17,37 +17,32 @@
 package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.osgi.service.log.LogService;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
 import com.ning.billing.ObjectType;
-import com.ning.billing.account.api.Account;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.junction.api.BlockingState;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessOverdueStatusFactory;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessOverdueStatusModelDao;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
 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.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
 public class BusinessOverdueStatusDao extends BusinessAnalyticsDaoBase {
 
+    private final LogService logService;
+    private final BusinessOverdueStatusFactory bosFactory;
+
     public BusinessOverdueStatusDao(final OSGIKillbillLogService logService,
                                     final OSGIKillbillAPI osgiKillbillAPI,
                                     final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
+        this.logService = logService;
+        bosFactory = new BusinessOverdueStatusFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final ObjectType objectType, final CallContext context) throws AnalyticsRefreshException {
@@ -59,14 +54,7 @@ public class BusinessOverdueStatusDao extends BusinessAnalyticsDaoBase {
     }
 
     private void updateForBundle(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        final Account account = getAccount(accountId, context);
-
-        final Collection<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(accountId, context);
-        final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses = new LinkedList<BusinessOverdueStatusModelDao>();
-        for (final SubscriptionBundle bundle : bundles) {
-            // Recompute all blocking states for that bundle
-            businessOverdueStatuses.addAll(createBusinessOverdueStatuses(account, bundle, context));
-        }
+        final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses = bosFactory.createBusinessOverdueStatuses(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
             @Override
@@ -89,39 +77,4 @@ public class BusinessOverdueStatusDao extends BusinessAnalyticsDaoBase {
             transactional.create(bst.getTableName(), bst, context);
         }
     }
-
-    private Collection<BusinessOverdueStatusModelDao> createBusinessOverdueStatuses(final Account account,
-                                                                                    final SubscriptionBundle subscriptionBundle,
-                                                                                    final CallContext context) throws AnalyticsRefreshException {
-        final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses = new LinkedList<BusinessOverdueStatusModelDao>();
-
-        final List<BlockingState> blockingStatesOrdered = getBlockingHistory(subscriptionBundle.getId(), context);
-        if (blockingStatesOrdered.size() == 0) {
-            return businessOverdueStatuses;
-        }
-
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
-        final List<BlockingState> blockingStates = Lists.reverse(ImmutableList.<BlockingState>copyOf(blockingStatesOrdered));
-        DateTime previousStartDate = null;
-        for (final BlockingState state : blockingStates) {
-            final Long blockingStateRecordId = getBlockingStateRecordId(state.getId(), context);
-            final AuditLog creationAuditLog = getBlockingStateCreationAuditLog(state.getId(), context);
-            final BusinessOverdueStatusModelDao overdueStatus = new BusinessOverdueStatusModelDao(account,
-                                                                                                  accountRecordId,
-                                                                                                  subscriptionBundle,
-                                                                                                  state,
-                                                                                                  blockingStateRecordId,
-                                                                                                  previousStartDate,
-                                                                                                  creationAuditLog,
-                                                                                                  tenantRecordId,
-                                                                                                  reportGroup);
-            businessOverdueStatuses.add(overdueStatus);
-            previousStartDate = state.getTimestamp();
-        }
-
-        return businessOverdueStatuses;
-    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java
index b3fda1c..8f5dcf0 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessSubscriptionTransitionDao.java
@@ -17,27 +17,18 @@
 package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
-
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
-import com.ning.billing.account.api.Account;
-import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessAccountFactory;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessBundleSummaryFactory;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessSubscriptionTransitionFactory;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessAccountModelDao;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscription;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscriptionEvent;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscriptionTransitionModelDao;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
@@ -47,37 +38,38 @@ public class BusinessSubscriptionTransitionDao extends BusinessAnalyticsDaoBase 
 
     private final BusinessAccountDao businessAccountDao;
     private final BusinessBundleSummaryDao businessBundleSummaryDao;
+    private final BusinessAccountFactory bacFactory;
+    private final BusinessBundleSummaryFactory bbsFactory;
+    private final BusinessSubscriptionTransitionFactory bstFactory;
 
     public BusinessSubscriptionTransitionDao(final OSGIKillbillLogService logService,
                                              final OSGIKillbillAPI osgiKillbillAPI,
                                              final OSGIKillbillDataSource osgiKillbillDataSource,
                                              final BusinessAccountDao businessAccountDao) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
         this.businessAccountDao = businessAccountDao;
         this.businessBundleSummaryDao = new BusinessBundleSummaryDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
+        bbsFactory = new BusinessBundleSummaryFactory(logService, osgiKillbillAPI);
+        bstFactory = new BusinessSubscriptionTransitionFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        final Account account = getAccount(accountId, context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
         // Recompute the account record
-        final BusinessAccountModelDao bac = businessAccountDao.createBusinessAccount(account, context);
+        final BusinessAccountModelDao bac = bacFactory.createBusinessAccount(accountId, context);
 
         // Recompute all invoices and invoice items
-        final Collection<BusinessSubscriptionTransitionModelDao> bsts = createBusinessSubscriptionTransitions(account,
-                                                                                                              bac.getAccountRecordId(),
-                                                                                                              bac.getTenantRecordId(),
-                                                                                                              reportGroup,
-                                                                                                              context);
+        final Collection<BusinessSubscriptionTransitionModelDao> bsts = bstFactory.createBusinessSubscriptionTransitions(accountId,
+                                                                                                                         bac.getAccountRecordId(),
+                                                                                                                         bac.getTenantRecordId(),
+                                                                                                                         context);
 
         // Recompute the bundle summary records
-        final Collection<BusinessBundleSummaryModelDao> bbss = businessBundleSummaryDao.createBusinessBundleSummaries(account,
-                                                                                                                      bac.getAccountRecordId(),
-                                                                                                                      bsts,
-                                                                                                                      bac.getTenantRecordId(),
-                                                                                                                      reportGroup,
-                                                                                                                      context);
+        final Collection<BusinessBundleSummaryModelDao> bbss = bbsFactory.createBusinessBundleSummaries(accountId,
+                                                                                                        bac.getAccountRecordId(),
+                                                                                                        bsts,
+                                                                                                        bac.getTenantRecordId(),
+                                                                                                        context);
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
             @Override
             public Void inTransaction(final BusinessAnalyticsSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -112,82 +104,4 @@ public class BusinessSubscriptionTransitionDao extends BusinessAnalyticsDaoBase 
         // Update BAC
         businessAccountDao.updateInTransaction(bac, transactional, context);
     }
-
-    private Collection<BusinessSubscriptionTransitionModelDao> createBusinessSubscriptionTransitions(final Account account,
-                                                                                                     final Long accountRecordId,
-                                                                                                     final Long tenantRecordId,
-                                                                                                     @Nullable final ReportGroup reportGroup,
-                                                                                                     final CallContext context) throws AnalyticsRefreshException {
-        final Collection<BusinessSubscriptionTransitionModelDao> bsts = new LinkedList<BusinessSubscriptionTransitionModelDao>();
-
-        final List<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(account.getId(), context);
-        for (final SubscriptionBundle bundle : bundles) {
-            final Collection<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId(), context);
-            for (final Subscription subscription : subscriptions) {
-                final List<SubscriptionTransition> transitions = subscription.getAllTransitions();
-
-                BusinessSubscription prevNextSubscription = null;
-
-                // Ordered for us by entitlement
-                for (final SubscriptionTransition transition : transitions) {
-                    final BusinessSubscription nextSubscription = getBusinessSubscriptionFromTransition(account, transition);
-                    final BusinessSubscriptionTransitionModelDao bst = createBusinessSubscriptionTransition(account,
-                                                                                                            accountRecordId,
-                                                                                                            bundle,
-                                                                                                            transition,
-                                                                                                            prevNextSubscription,
-                                                                                                            nextSubscription,
-                                                                                                            tenantRecordId,
-                                                                                                            reportGroup,
-                                                                                                            context);
-                    if (bst != null) {
-                        bsts.add(bst);
-                        prevNextSubscription = nextSubscription;
-                    }
-                }
-            }
-        }
-
-        return bsts;
-    }
-
-    private BusinessSubscriptionTransitionModelDao createBusinessSubscriptionTransition(final Account account,
-                                                                                        final Long accountRecordId,
-                                                                                        final SubscriptionBundle subscriptionBundle,
-                                                                                        final SubscriptionTransition subscriptionTransition,
-                                                                                        @Nullable final BusinessSubscription prevNextSubscription,
-                                                                                        final BusinessSubscription nextSubscription,
-                                                                                        final Long tenantRecordId,
-                                                                                        @Nullable final ReportGroup reportGroup,
-                                                                                        final CallContext context) throws AnalyticsRefreshException {
-        final BusinessSubscriptionEvent businessEvent = BusinessSubscriptionEvent.fromTransition(subscriptionTransition);
-        if (businessEvent == null) {
-            return null;
-        }
-
-        final Long subscriptionEventRecordId = getSubscriptionEventRecordId(subscriptionTransition.getNextEventId(), context);
-        final AuditLog creationAuditLog = getSubscriptionEventCreationAuditLog(subscriptionTransition.getNextEventId(), context);
-
-        return new BusinessSubscriptionTransitionModelDao(account,
-                                                          accountRecordId,
-                                                          subscriptionBundle,
-                                                          subscriptionTransition,
-                                                          subscriptionEventRecordId,
-                                                          subscriptionTransition.getRequestedTransitionTime(),
-                                                          businessEvent,
-                                                          prevNextSubscription,
-                                                          nextSubscription,
-                                                          creationAuditLog,
-                                                          tenantRecordId,
-                                                          reportGroup);
-    }
-
-    private BusinessSubscription getBusinessSubscriptionFromTransition(final Account account, final SubscriptionTransition subscriptionTransition) {
-        return new BusinessSubscription(subscriptionTransition.getNextPlan(),
-                                        subscriptionTransition.getNextPhase(),
-                                        subscriptionTransition.getNextPriceList(),
-                                        account.getCurrency(),
-                                        subscriptionTransition.getEffectiveTransitionTime(),
-                                        subscriptionTransition.getNextState());
-    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java
index 5b843a0..4516b65 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessTagDao.java
@@ -17,36 +17,35 @@
 package com.ning.billing.osgi.bundles.analytics.dao;
 
 import java.util.Collection;
-import java.util.LinkedList;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 
-import com.ning.billing.account.api.Account;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
-import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessAccountFactory;
+import com.ning.billing.osgi.bundles.analytics.dao.factory.BusinessTagFactory;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessTagModelDao;
-import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDefinition;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessTagDao extends BusinessAnalyticsDaoBase {
 
+    private final BusinessAccountFactory bacFactory;
+    private final BusinessTagFactory bTagFactory;
+
     public BusinessTagDao(final OSGIKillbillLogService logService,
                           final OSGIKillbillAPI osgiKillbillAPI,
                           final OSGIKillbillDataSource osgiKillbillDataSource) {
-        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        super(osgiKillbillDataSource);
+        bacFactory = new BusinessAccountFactory(logService, osgiKillbillAPI);
+        bTagFactory = new BusinessTagFactory(logService, osgiKillbillAPI);
     }
 
     public void update(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
-        final Account account = getAccount(accountId, context);
-
-        final Collection<BusinessTagModelDao> tagModelDaos = createBusinessTags(account, context);
+        final Collection<BusinessTagModelDao> tagModelDaos = bTagFactory.createBusinessTags(accountId, context);
 
         sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
             @Override
@@ -70,30 +69,4 @@ public class BusinessTagDao extends BusinessAnalyticsDaoBase {
             transactional.create(tagModelDao.getTableName(), tagModelDao, context);
         }
     }
-
-    private Collection<BusinessTagModelDao> createBusinessTags(final Account account, final CallContext context) throws AnalyticsRefreshException {
-        final Long accountRecordId = getAccountRecordId(account.getId(), context);
-        final Long tenantRecordId = getTenantRecordId(context);
-        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
-
-        final Collection<Tag> tags = getTagsForAccount(account.getId(), context);
-
-        final Collection<BusinessTagModelDao> tagModelDaos = new LinkedList<BusinessTagModelDao>();
-        for (final Tag tag : tags) {
-            final Long tagRecordId = getTagRecordId(tag.getId(), context);
-            final TagDefinition tagDefinition = getTagDefinition(tag.getTagDefinitionId(), context);
-            final AuditLog creationAuditLog = getTagCreationAuditLog(tag.getId(), context);
-            final BusinessTagModelDao tagModelDao = BusinessTagModelDao.create(account,
-                                                                               accountRecordId,
-                                                                               tag,
-                                                                               tagRecordId,
-                                                                               tagDefinition,
-                                                                               creationAuditLog,
-                                                                               tenantRecordId,
-                                                                               reportGroup);
-            tagModelDaos.add(tagModelDao);
-        }
-
-        return tagModelDaos;
-    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessAccountFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessAccountFactory.java
new file mode 100644
index 0000000..3154362
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessAccountFactory.java
@@ -0,0 +1,101 @@
+/*
+ * 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.factory;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.invoice.api.Invoice;
+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.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessAccountFactory extends BusinessFactoryBase {
+
+    public BusinessAccountFactory(final OSGIKillbillLogService logService,
+                                  final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    public BusinessAccountModelDao createBusinessAccount(final UUID accountId,
+                                                         final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+
+        // Retrieve the account creation audit log
+        final AuditLog creationAuditLog = getAccountCreationAuditLog(account.getId(), context);
+
+        // Retrieve the account balance
+        // Note: since we retrieve the invoices below, we could compute it ourselves and avoid fetching the invoices
+        // twice, but that way the computation logic is owned by invoice
+        final BigDecimal accountBalance = getAccountBalance(account.getId(), context);
+
+        // Retrieve invoices information
+        Invoice lastInvoice = null;
+        final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
+        for (final Invoice invoice : invoices) {
+            if (lastInvoice == null || invoice.getInvoiceDate().isAfter(lastInvoice.getInvoiceDate())) {
+                lastInvoice = invoice;
+            }
+        }
+
+        // Retrieve payments information
+        Payment lastPayment = null;
+        final Collection<Payment> payments = getPaymentsByAccountId(account.getId(), context);
+        for (final Payment payment : payments) {
+            if (lastPayment == null || payment.getEffectiveDate().isAfter(lastPayment.getEffectiveDate())) {
+                lastPayment = payment;
+            }
+        }
+
+        final List<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(account.getId(), context);
+        int nbActiveBundles = 0;
+        for (final SubscriptionBundle bundle : bundles) {
+            final Collection<Subscription> subscriptionsForBundle = getSubscriptionsForBundle(bundle.getId(), context);
+            for (final Subscription subscription : subscriptionsForBundle) {
+                if (ProductCategory.BASE.equals(subscription.getCategory()) &&
+                    !(subscription.getEndDate() != null && !subscription.getEndDate().isAfterNow())) {
+                    nbActiveBundles++;
+                }
+            }
+        }
+
+        final Long accountRecordId = getAccountRecordId(account.getId(), context);
+        final Long tenantRecordId = getTenantRecordId(context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        return new BusinessAccountModelDao(account,
+                                           accountRecordId,
+                                           accountBalance,
+                                           lastInvoice,
+                                           lastPayment,
+                                           nbActiveBundles,
+                                           creationAuditLog,
+                                           tenantRecordId,
+                                           reportGroup);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessBundleSummaryFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessBundleSummaryFactory.java
new file mode 100644
index 0000000..57a7599
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessBundleSummaryFactory.java
@@ -0,0 +1,123 @@
+/*
+ * 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.factory;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessBundleSummaryModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+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.Iterables;
+import com.google.common.collect.Ordering;
+
+public class BusinessBundleSummaryFactory extends BusinessFactoryBase {
+
+    public BusinessBundleSummaryFactory(final OSGIKillbillLogService logService,
+                                        final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    public Collection<BusinessBundleSummaryModelDao> createBusinessBundleSummaries(final UUID accountId,
+                                                                                   final Long accountRecordId,
+                                                                                   final Collection<BusinessSubscriptionTransitionModelDao> bsts,
+                                                                                   final Long tenantRecordId,
+                                                                                   final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        final Map<UUID, Integer> rankForBundle = new LinkedHashMap<UUID, Integer>();
+        final Map<UUID, BusinessSubscriptionTransitionModelDao> bstForBundle = new LinkedHashMap<UUID, BusinessSubscriptionTransitionModelDao>();
+        filterBstsForBasePlans(bsts, rankForBundle, bstForBundle);
+
+        final Collection<BusinessBundleSummaryModelDao> bbss = new LinkedList<BusinessBundleSummaryModelDao>();
+        for (final BusinessSubscriptionTransitionModelDao bst : bstForBundle.values()) {
+            final BusinessBundleSummaryModelDao bbs = buildBBS(account,
+                                                               accountRecordId,
+                                                               bst,
+                                                               rankForBundle.get(bst.getBundleId()),
+                                                               tenantRecordId,
+                                                               reportGroup,
+                                                               context);
+            bbss.add(bbs);
+        }
+        return bbss;
+    }
+
+    @VisibleForTesting
+    void filterBstsForBasePlans(final Collection<BusinessSubscriptionTransitionModelDao> bsts, final Map<UUID, Integer> rankForBundle, final Map<UUID, BusinessSubscriptionTransitionModelDao> bstForBundle) {// Find bsts for BASE subscriptions only and sort them using the next start date
+        final Collection<BusinessSubscriptionTransitionModelDao> sortedBundlesBst = Ordering.from(new Comparator<BusinessSubscriptionTransitionModelDao>() {
+            @Override
+            public int compare(final BusinessSubscriptionTransitionModelDao o1, final BusinessSubscriptionTransitionModelDao o2) {
+                return o1.getNextStartDate().compareTo(o2.getNextStartDate());
+            }
+        }).sortedCopy(Iterables.filter(bsts, new Predicate<BusinessSubscriptionTransitionModelDao>() {
+            @Override
+            public boolean apply(final BusinessSubscriptionTransitionModelDao input) {
+                return ProductCategory.BASE.toString().equals(input.getNextProductCategory());
+            }
+        }));
+
+        UUID lastBundleId = null;
+        Integer lastBundleRank = 0;
+        for (final BusinessSubscriptionTransitionModelDao bst : sortedBundlesBst) {
+            // Note that sortedBundlesBst is not ordered bundle by bundle, i.e. we may have:
+            // bundleId1 CREATE, bundleId2 CREATE, bundleId1 PHASE, bundleId3 CREATE bundleId2 PHASE
+            if (lastBundleId == null || (!lastBundleId.equals(bst.getBundleId()) && rankForBundle.get(bst.getBundleId()) == null)) {
+                lastBundleRank++;
+                lastBundleId = bst.getBundleId();
+                rankForBundle.put(lastBundleId, lastBundleRank);
+            }
+
+            if (bstForBundle.get(bst.getBundleId()) == null ||
+                bstForBundle.get(bst.getBundleId()).getNextStartDate().isBefore(bst.getNextStartDate())) {
+                bstForBundle.put(bst.getBundleId(), bst);
+            }
+        }
+    }
+
+    private BusinessBundleSummaryModelDao buildBBS(final Account account, final Long accountRecordId, final BusinessSubscriptionTransitionModelDao bst, final Integer bundleAccountRank, final Long tenantRecordId, final ReportGroup reportGroup, final CallContext context) throws AnalyticsRefreshException {
+        final SubscriptionBundle bundle = getSubscriptionBundle(bst.getBundleId(), context);
+        final Long bundleRecordId = getBundleRecordId(bundle.getId(), context);
+        final AuditLog creationAuditLog = getBundleCreationAuditLog(bundle.getId(), context);
+
+        return new BusinessBundleSummaryModelDao(account,
+                                                 accountRecordId,
+                                                 bundle,
+                                                 bundleRecordId,
+                                                 bundleAccountRank,
+                                                 bst,
+                                                 creationAuditLog,
+                                                 tenantRecordId,
+                                                 reportGroup);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFactoryBase.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFactoryBase.java
new file mode 100644
index 0000000..77f0969
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFactoryBase.java
@@ -0,0 +1,29 @@
+/*
+ * 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.factory;
+
+import com.ning.billing.osgi.bundles.analytics.BusinessAnalyticsBase;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessFactoryBase extends BusinessAnalyticsBase {
+
+    public BusinessFactoryBase(final OSGIKillbillLogService logService,
+                               final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java
new file mode 100644
index 0000000..ce8adab
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessFieldFactory.java
@@ -0,0 +1,66 @@
+/*
+ * 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.factory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessFieldModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessFieldFactory extends BusinessFactoryBase {
+
+    public BusinessFieldFactory(final OSGIKillbillLogService logService,
+                                final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    public Collection<BusinessFieldModelDao> createBusinessFields(final UUID accountId,
+                                                                  final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+
+        final Long accountRecordId = getAccountRecordId(account.getId(), context);
+        final Long tenantRecordId = getTenantRecordId(context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        final Collection<CustomField> fields = getFieldsForAccount(account.getId(), context);
+
+        final Collection<BusinessFieldModelDao> fieldModelDaos = new LinkedList<BusinessFieldModelDao>();
+        for (final CustomField field : fields) {
+            final Long customFieldRecordId = getFieldRecordId(field.getId(), context);
+            final AuditLog creationAuditLog = getFieldCreationAuditLog(field.getId(), context);
+            final BusinessFieldModelDao fieldModelDao = BusinessFieldModelDao.create(account,
+                                                                                     accountRecordId,
+                                                                                     field,
+                                                                                     customFieldRecordId,
+                                                                                     creationAuditLog,
+                                                                                     tenantRecordId,
+                                                                                     reportGroup);
+            fieldModelDaos.add(fieldModelDao);
+        }
+
+        return fieldModelDaos;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
new file mode 100644
index 0000000..4f8a46b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
@@ -0,0 +1,592 @@
+/*
+ * 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.factory;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+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 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.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.osgi.bundles.analytics.utils.BusinessInvoiceUtils;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TenantContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+
+import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isAccountCreditItem;
+import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isCharge;
+import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isInvoiceAdjustmentItem;
+import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isInvoiceItemAdjustmentItem;
+import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isRevenueRecognizable;
+
+public class BusinessInvoiceFactory extends BusinessFactoryBase {
+
+    public BusinessInvoiceFactory(final OSGIKillbillLogService logService,
+                                  final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    /**
+     * Create business invoices and invoice items to record. Note that these POJOs are incomplete
+     * (denormalized payment fields have not yet been populated)
+     *
+     * @param accountId current accountId refreshed
+     * @param context   call context
+     * @return all business invoice and invoice items to create
+     * @throws com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException
+     *
+     */
+    public Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> createBusinessInvoicesAndInvoiceItems(final UUID accountId,
+                                                                                                                           final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+        final Long accountRecordId = getAccountRecordId(account.getId(), context);
+        final Long tenantRecordId = getTenantRecordId(context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        // Lookup the invoices for that account
+        final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
+
+        // All invoice items across all invoices for that accounr (we need to be able to reference items across multiple invoices)
+        final Collection<InvoiceItem> allInvoiceItems = new LinkedList<InvoiceItem>();
+        // Convenient mapping invoice_id -> invoice
+        final Map<UUID, Invoice> invoiceIdToInvoiceMappings = new LinkedHashMap<UUID, Invoice>();
+        for (final Invoice invoice : invoices) {
+            invoiceIdToInvoiceMappings.put(invoice.getId(), invoice);
+            allInvoiceItems.addAll(invoice.getInvoiceItems());
+        }
+
+        // Sanitize (cherry-pick, merge) the items
+        final Collection<InvoiceItem> sanitizedInvoiceItems = sanitizeInvoiceItems(allInvoiceItems);
+
+        // Create the business invoice items. These are incomplete (the denormalized invoice fields haven't been computed yet)
+        final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItemsForInvoiceId = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
+        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 (businessInvoiceItemsForInvoiceId.get(invoice.getId()) == null) {
+                    businessInvoiceItemsForInvoiceId.put(invoice.getId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
+                }
+                businessInvoiceItemsForInvoiceId.get(invoice.getId()).add(businessInvoiceItem);
+            }
+        }
+
+        // Now, create the business invoices
+        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessRecords = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>>();
+        for (final Invoice invoice : invoices) {
+            final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems = businessInvoiceItemsForInvoiceId.get(invoice.getId());
+            if (businessInvoiceItems == null) {
+                continue;
+            }
+
+            final BusinessInvoiceModelDao businessInvoice = createBusinessInvoice(account, invoice, businessInvoiceItems, accountRecordId, tenantRecordId, reportGroup, context);
+            businessRecords.put(businessInvoice, businessInvoiceItems);
+        }
+
+        return businessRecords;
+    }
+
+    private BusinessInvoiceModelDao createBusinessInvoice(final Account account,
+                                                          final Invoice invoice,
+                                                          final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
+                                                          final Long accountRecordId,
+                                                          final Long tenantRecordId,
+                                                          @Nullable final ReportGroup reportGroup,
+                                                          final CallContext context) throws AnalyticsRefreshException {
+        final Long invoiceRecordId = getInvoiceRecordId(invoice.getId(), context);
+        final AuditLog creationAuditLog = getInvoiceCreationAuditLog(invoice.getId(), context);
+
+        final BigDecimal amountCharged = BusinessInvoiceUtils.computeInvoiceAmountCharged(businessInvoiceItems);
+        final BigDecimal originalAmountCharged = BusinessInvoiceUtils.computeInvoiceOriginalAmountCharged(businessInvoiceItems);
+        final BigDecimal amountCredited = BusinessInvoiceUtils.computeInvoiceAmountCredited(businessInvoiceItems);
+
+        return new BusinessInvoiceModelDao(account,
+                                           accountRecordId,
+                                           invoice,
+                                           amountCharged,
+                                           originalAmountCharged,
+                                           amountCredited,
+                                           invoiceRecordId,
+                                           creationAuditLog,
+                                           tenantRecordId,
+                                           reportGroup);
+    }
+
+    private BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
+                                                                      final Invoice invoice,
+                                                                      final InvoiceItem invoiceItem,
+                                                                      final Collection<InvoiceItem> otherInvoiceItems,
+                                                                      final Long accountRecordId,
+                                                                      final Long tenantRecordId,
+                                                                      @Nullable final ReportGroup reportGroup,
+                                                                      final TenantContext context) throws AnalyticsRefreshException {
+        SubscriptionBundle bundle = null;
+        // Subscription and bundle could be null for e.g. credits or adjustments
+        if (invoiceItem.getBundleId() != null) {
+            bundle = getSubscriptionBundle(invoiceItem.getBundleId(), context);
+        }
+
+        Plan plan = null;
+        if (Strings.emptyToNull(invoiceItem.getPlanName()) != null) {
+            plan = getPlanFromInvoiceItem(invoiceItem, context);
+        }
+
+        PlanPhase planPhase = null;
+        if (invoiceItem.getSubscriptionId() != null && Strings.emptyToNull(invoiceItem.getPhaseName()) != null) {
+            planPhase = getPlanPhaseFromInvoiceItem(invoiceItem, context);
+        }
+
+        final Long invoiceItemRecordId = getInvoiceItemRecordId(invoiceItem.getId(), context);
+        final AuditLog creationAuditLog = getInvoiceItemCreationAuditLog(invoiceItem.getId(), context);
+
+        return createBusinessInvoiceItem(account,
+                                         invoice,
+                                         invoiceItem,
+                                         otherInvoiceItems,
+                                         bundle,
+                                         plan,
+                                         planPhase,
+                                         invoiceItemRecordId,
+                                         creationAuditLog,
+                                         accountRecordId,
+                                         tenantRecordId,
+                                         reportGroup,
+                                         context);
+    }
+
+    @VisibleForTesting
+    BusinessInvoiceItemBaseModelDao createBusinessInvoiceItem(final Account account,
+                                                              final Invoice invoice,
+                                                              final InvoiceItem invoiceItem,
+                                                              final Collection<InvoiceItem> otherInvoiceItems,
+                                                              @Nullable final SubscriptionBundle bundle,
+                                                              @Nullable final Plan plan,
+                                                              @Nullable final PlanPhase planPhase,
+                                                              final Long invoiceItemRecordId,
+                                                              final AuditLog creationAuditLog,
+                                                              final Long accountRecordId,
+                                                              final Long tenantRecordId,
+                                                              final ReportGroup reportGroup,
+                                                              final TenantContext context) throws AnalyticsRefreshException {
+        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, otherInvoiceItems)) {
+            businessInvoiceItemType = BusinessInvoiceItemType.INVOICE_ADJUSTMENT;
+        } else {
+            // We don't care
+            return null;
+        }
+
+        final Boolean revenueRecognizable = isRevenueRecognizable(invoiceItem, otherInvoiceItems);
+
+        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,
+                                                      plan,
+                                                      planPhase,
+                                                      creationAuditLog,
+                                                      tenantRecordId,
+                                                      reportGroup);
+    }
+
+    /**
+     * Filter and transform the original invoice items for Analytics purposes. We mainly
+     * merge REPAIR_ADJ items with reparation items (reparees) to create item adjustments.
+     *
+     * @param allInvoiceItems all items for the current account
+     * @return invoice items interesting for Analytics purposes
+     */
+    @VisibleForTesting
+    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) {
+            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 : allInvoiceItems) {
+            // Skip non-repaired items
+            if (!repairedInvoiceItemIdToRepairInvoiceItemMappings.keySet().contains(repairedInvoiceItem.getId())) {
+                continue;
+            }
+
+            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 &&
+                    // FIXED items have a null end date
+                    ((repairedInvoiceItem.getEndDate() == null && invoiceItem.getEndDate() == null) ||
+                     (repairedInvoiceItem.getEndDate() != null && invoiceItem.getEndDate() != null && !repairedInvoiceItem.getEndDate().isBefore(invoiceItem.getEndDate()))) &&
+                    !repairedInvoiceItem.getId().equals(invoiceItem.getId())) {
+                    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!");
+            }
+        }
+
+        // We now need to adjust the CBA_ADJ for the repair items
+        final Set<UUID> cbasToIgnore = new HashSet<UUID>();
+        final Collection<AdjustedCBAInvoiceItem> newCbasToAdd = new LinkedList<AdjustedCBAInvoiceItem>();
+        for (final InvoiceItem cbaInvoiceItem : allInvoiceItems) {
+            if (!InvoiceItemType.CBA_ADJ.equals(cbaInvoiceItem.getInvoiceItemType())) {
+                continue;
+            }
+
+            for (final InvoiceItem invoiceItem : allInvoiceItems) {
+                if (reparationInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
+                    final InvoiceItem repairInvoiceItem = reparationInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
+                    final InvoiceItem reparationInvoiceItem = invoiceItem;
+                    // Au petit bonheur la chance... There is nothing else against to compare
+                    if (repairInvoiceItem.getAmount().negate().compareTo(cbaInvoiceItem.getAmount()) == 0) {
+                        cbasToIgnore.add(cbaInvoiceItem.getId());
+                        newCbasToAdd.add(new AdjustedCBAInvoiceItem(cbaInvoiceItem, cbaInvoiceItem.getAmount().add(reparationInvoiceItem.getAmount().negate()), reparationInvoiceItem.getId()));
+
+                        // Now, fiddle with the CBA used on the reparation invoice
+                        for (final InvoiceItem cbaUsedOnNextInvoiceItem : allInvoiceItems) {
+                            if (!InvoiceItemType.CBA_ADJ.equals(cbaUsedOnNextInvoiceItem.getInvoiceItemType()) ||
+                                !cbaUsedOnNextInvoiceItem.getInvoiceId().equals(reparationInvoiceItem.getInvoiceId())) {
+                                continue;
+                            }
+
+                            // Au petit bonheur la chance... There is nothing else against to compare. Take the first one again?
+                            cbasToIgnore.add(cbaUsedOnNextInvoiceItem.getId());
+                            newCbasToAdd.add(new AdjustedCBAInvoiceItem(cbaUsedOnNextInvoiceItem, cbaUsedOnNextInvoiceItem.getAmount().add(reparationInvoiceItem.getAmount()), reparationInvoiceItem.getId()));
+                            break;
+                        }
+
+                        // Break from the inner loop only
+                        break;
+                    }
+                }
+            }
+        }
+
+
+        // Filter the invoice items for analytics
+        final Collection<InvoiceItem> invoiceItemsForAnalytics = new LinkedList<InvoiceItem>();
+        for (final InvoiceItem invoiceItem : allInvoiceItems) {
+            if (cbasToIgnore.contains(invoiceItem.getId())) {
+                // We don't care
+            } else 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);
+            }
+        }
+        invoiceItemsForAnalytics.addAll(newCbasToAdd);
+
+        return invoiceItemsForAnalytics;
+    }
+
+    private class AdjustedCBAInvoiceItem implements InvoiceItem {
+
+        private final InvoiceItem cbaInvoiceItem;
+        private final BigDecimal amount;
+        private final UUID reparationItemId;
+
+        private AdjustedCBAInvoiceItem(final InvoiceItem cbaInvoiceItem,
+                                       final BigDecimal amount,
+                                       final UUID reparationItemId) {
+            this.cbaInvoiceItem = cbaInvoiceItem;
+            this.amount = amount;
+            this.reparationItemId = reparationItemId;
+        }
+
+        @Override
+        public InvoiceItemType getInvoiceItemType() {
+            return InvoiceItemType.CBA_ADJ;
+        }
+
+        @Override
+        public UUID getInvoiceId() {
+            return cbaInvoiceItem.getInvoiceId();
+        }
+
+        @Override
+        public UUID getAccountId() {
+            return cbaInvoiceItem.getAccountId();
+        }
+
+        @Override
+        public LocalDate getStartDate() {
+            return cbaInvoiceItem.getStartDate();
+        }
+
+        @Override
+        public LocalDate getEndDate() {
+            return cbaInvoiceItem.getStartDate();
+        }
+
+        @Override
+        public BigDecimal getAmount() {
+            return amount;
+        }
+
+        @Override
+        public Currency getCurrency() {
+            return cbaInvoiceItem.getCurrency();
+        }
+
+        @Override
+        public String getDescription() {
+            return cbaInvoiceItem.getDescription();
+        }
+
+        @Override
+        public UUID getBundleId() {
+            return cbaInvoiceItem.getBundleId();
+        }
+
+        @Override
+        public UUID getSubscriptionId() {
+            return cbaInvoiceItem.getSubscriptionId();
+        }
+
+        @Override
+        public String getPlanName() {
+            return cbaInvoiceItem.getPlanName();
+        }
+
+        @Override
+        public String getPhaseName() {
+            return cbaInvoiceItem.getPhaseName();
+        }
+
+        @Override
+        public BigDecimal getRate() {
+            return cbaInvoiceItem.getRate();
+        }
+
+        @Override
+        public UUID getLinkedItemId() {
+            return cbaInvoiceItem.getLinkedItemId();
+        }
+
+        @Override
+        public boolean matches(final Object other) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public UUID getId() {
+            return cbaInvoiceItem.getId();
+        }
+
+        public UUID getSecondId() {
+            return reparationItemId;
+        }
+
+        @Override
+        public DateTime getCreatedDate() {
+            return cbaInvoiceItem.getCreatedDate();
+        }
+
+        @Override
+        public DateTime getUpdatedDate() {
+            return cbaInvoiceItem.getUpdatedDate();
+        }
+    }
+
+    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 boolean matches(final Object other) {
+            throw new UnsupportedOperationException();
+        }
+
+        @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/factory/BusinessInvoiceItemFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceItemFactory.java
new file mode 100644
index 0000000..89b4f55
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceItemFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.factory;
+
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessInvoiceItemFactory extends BusinessFactoryBase {
+
+    public BusinessInvoiceItemFactory(final OSGIKillbillLogService logService,
+                                      final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoicePaymentFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoicePaymentFactory.java
new file mode 100644
index 0000000..11856f2
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoicePaymentFactory.java
@@ -0,0 +1,109 @@
+/*
+ * 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.factory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentBaseModelDao;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.payment.api.Payment;
+import com.ning.billing.payment.api.PaymentMethod;
+import com.ning.billing.payment.api.Refund;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessInvoicePaymentFactory extends BusinessFactoryBase {
+
+    public BusinessInvoicePaymentFactory(final OSGIKillbillLogService logService,
+                                         final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+
+    public Collection<BusinessInvoicePaymentBaseModelDao> createBusinessInvoicePayments(final UUID accountId,
+                                                                                        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
+                                                                                        final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+
+        final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments = new LinkedList<BusinessInvoicePaymentBaseModelDao>();
+
+        final Long accountRecordId = getAccountRecordId(account.getId(), context);
+        final Long tenantRecordId = getTenantRecordId(context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        final Collection<InvoicePayment> invoicePayments = getAccountInvoicePayments(account.getId(), context);
+        for (final InvoicePayment invoicePayment : invoicePayments) {
+            final BusinessInvoicePaymentBaseModelDao businessInvoicePayment = createBusinessInvoicePayment(account,
+                                                                                                           invoicePayment,
+                                                                                                           businessInvoices,
+                                                                                                           accountRecordId,
+                                                                                                           tenantRecordId,
+                                                                                                           reportGroup,
+                                                                                                           context);
+            if (businessInvoicePayment != null) {
+                businessInvoicePayments.add(businessInvoicePayment);
+            }
+        }
+
+        return businessInvoicePayments;
+    }
+
+    private BusinessInvoicePaymentBaseModelDao createBusinessInvoicePayment(final Account account,
+                                                                            final InvoicePayment invoicePayment,
+                                                                            final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
+                                                                            final Long accountRecordId,
+                                                                            final Long tenantRecordId,
+                                                                            @Nullable final ReportGroup reportGroup,
+                                                                            final CallContext context) throws AnalyticsRefreshException {
+        final Long invoicePaymentRecordId = getInvoicePaymentRecordId(invoicePayment.getId(), context);
+
+        final Payment payment = getPaymentWithPluginInfo(invoicePayment.getPaymentId(), context);
+        Refund refund = null;
+        if (invoicePayment.getPaymentCookieId() != null) {
+            refund = getRefundWithPluginInfo(invoicePayment.getPaymentCookieId(), context);
+        }
+
+        final Invoice invoice = getInvoice(invoicePayment.getInvoiceId(), context);
+        final PaymentMethod paymentMethod = getPaymentMethod(payment.getPaymentMethodId(), context);
+        final AuditLog creationAuditLog = getInvoicePaymentCreationAuditLog(invoicePayment.getId(), context);
+
+        return BusinessInvoicePaymentBaseModelDao.create(account,
+                                                         accountRecordId,
+                                                         invoice,
+                                                         invoicePayment,
+                                                         invoicePaymentRecordId,
+                                                         payment,
+                                                         refund,
+                                                         paymentMethod,
+                                                         creationAuditLog,
+                                                         tenantRecordId,
+                                                         reportGroup);
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessOverdueStatusFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessOverdueStatusFactory.java
new file mode 100644
index 0000000..d346cc1
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessOverdueStatusFactory.java
@@ -0,0 +1,95 @@
+/*
+ * 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.factory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessOverdueStatusModelDao;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+public class BusinessOverdueStatusFactory extends BusinessFactoryBase {
+
+    public BusinessOverdueStatusFactory(final OSGIKillbillLogService logService,
+                                        final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    public Collection<BusinessOverdueStatusModelDao> createBusinessOverdueStatuses(final UUID accountId,
+                                                                                   final CallContext context) throws AnalyticsRefreshException {
+        final Collection<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(accountId, context);
+        final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses = new LinkedList<BusinessOverdueStatusModelDao>();
+        for (final SubscriptionBundle bundle : bundles) {
+            // Recompute all blocking states for that bundle
+            businessOverdueStatuses.addAll(createBusinessOverdueStatusesForBundle(accountId, bundle, context));
+        }
+
+        return businessOverdueStatuses;
+    }
+
+    private Collection<BusinessOverdueStatusModelDao> createBusinessOverdueStatusesForBundle(final UUID accountId,
+                                                                                             final SubscriptionBundle subscriptionBundle,
+                                                                                             final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+
+        final Collection<BusinessOverdueStatusModelDao> businessOverdueStatuses = new LinkedList<BusinessOverdueStatusModelDao>();
+
+        final List<BlockingState> blockingStatesOrdered = getBlockingHistory(subscriptionBundle.getId(), context);
+        if (blockingStatesOrdered.size() == 0) {
+            return businessOverdueStatuses;
+        }
+
+        final Long accountRecordId = getAccountRecordId(account.getId(), context);
+        final Long tenantRecordId = getTenantRecordId(context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        final List<BlockingState> blockingStates = Lists.reverse(ImmutableList.<BlockingState>copyOf(blockingStatesOrdered));
+        DateTime previousStartDate = null;
+        for (final BlockingState state : blockingStates) {
+            final Long blockingStateRecordId = getBlockingStateRecordId(state.getId(), context);
+            final AuditLog creationAuditLog = getBlockingStateCreationAuditLog(state.getId(), context);
+            final BusinessOverdueStatusModelDao overdueStatus = new BusinessOverdueStatusModelDao(account,
+                                                                                                  accountRecordId,
+                                                                                                  subscriptionBundle,
+                                                                                                  state,
+                                                                                                  blockingStateRecordId,
+                                                                                                  previousStartDate,
+                                                                                                  creationAuditLog,
+                                                                                                  tenantRecordId,
+                                                                                                  reportGroup);
+            businessOverdueStatuses.add(overdueStatus);
+            previousStartDate = state.getTimestamp();
+        }
+
+        return businessOverdueStatuses;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java
new file mode 100644
index 0000000..7cbc5f4
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessSubscriptionTransitionFactory.java
@@ -0,0 +1,126 @@
+/*
+ * 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.factory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscription;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscriptionEvent;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessSubscriptionTransitionModelDao;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessSubscriptionTransitionFactory extends BusinessFactoryBase {
+
+    public BusinessSubscriptionTransitionFactory(final OSGIKillbillLogService logService,
+                                                 final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    public Collection<BusinessSubscriptionTransitionModelDao> createBusinessSubscriptionTransitions(final UUID accountId,
+                                                                                                    final Long accountRecordId,
+                                                                                                    final Long tenantRecordId,
+                                                                                                    final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        final Collection<BusinessSubscriptionTransitionModelDao> bsts = new LinkedList<BusinessSubscriptionTransitionModelDao>();
+
+        final List<SubscriptionBundle> bundles = getSubscriptionBundlesForAccount(account.getId(), context);
+        for (final SubscriptionBundle bundle : bundles) {
+            final Collection<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId(), context);
+            for (final Subscription subscription : subscriptions) {
+                final List<SubscriptionTransition> transitions = subscription.getAllTransitions();
+
+                BusinessSubscription prevNextSubscription = null;
+
+                // Ordered for us by entitlement
+                for (final SubscriptionTransition transition : transitions) {
+                    final BusinessSubscription nextSubscription = getBusinessSubscriptionFromTransition(account, transition);
+                    final BusinessSubscriptionTransitionModelDao bst = createBusinessSubscriptionTransition(account,
+                                                                                                            accountRecordId,
+                                                                                                            bundle,
+                                                                                                            transition,
+                                                                                                            prevNextSubscription,
+                                                                                                            nextSubscription,
+                                                                                                            tenantRecordId,
+                                                                                                            reportGroup,
+                                                                                                            context);
+                    if (bst != null) {
+                        bsts.add(bst);
+                        prevNextSubscription = nextSubscription;
+                    }
+                }
+            }
+        }
+
+        return bsts;
+    }
+
+    private BusinessSubscriptionTransitionModelDao createBusinessSubscriptionTransition(final Account account,
+                                                                                        final Long accountRecordId,
+                                                                                        final SubscriptionBundle subscriptionBundle,
+                                                                                        final SubscriptionTransition subscriptionTransition,
+                                                                                        @Nullable final BusinessSubscription prevNextSubscription,
+                                                                                        final BusinessSubscription nextSubscription,
+                                                                                        final Long tenantRecordId,
+                                                                                        @Nullable final ReportGroup reportGroup,
+                                                                                        final CallContext context) throws AnalyticsRefreshException {
+        final BusinessSubscriptionEvent businessEvent = BusinessSubscriptionEvent.fromTransition(subscriptionTransition);
+        if (businessEvent == null) {
+            return null;
+        }
+
+        final Long subscriptionEventRecordId = getSubscriptionEventRecordId(subscriptionTransition.getNextEventId(), context);
+        final AuditLog creationAuditLog = getSubscriptionEventCreationAuditLog(subscriptionTransition.getNextEventId(), context);
+
+        return new BusinessSubscriptionTransitionModelDao(account,
+                                                          accountRecordId,
+                                                          subscriptionBundle,
+                                                          subscriptionTransition,
+                                                          subscriptionEventRecordId,
+                                                          subscriptionTransition.getRequestedTransitionTime(),
+                                                          businessEvent,
+                                                          prevNextSubscription,
+                                                          nextSubscription,
+                                                          creationAuditLog,
+                                                          tenantRecordId,
+                                                          reportGroup);
+    }
+
+    private BusinessSubscription getBusinessSubscriptionFromTransition(final Account account, final SubscriptionTransition subscriptionTransition) {
+        return new BusinessSubscription(subscriptionTransition.getNextPlan(),
+                                        subscriptionTransition.getNextPhase(),
+                                        subscriptionTransition.getNextPriceList(),
+                                        account.getCurrency(),
+                                        subscriptionTransition.getEffectiveTransitionTime(),
+                                        subscriptionTransition.getNextState());
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java
new file mode 100644
index 0000000..1df645d
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessTagFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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.factory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.UUID;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsRefreshException;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessModelDaoBase.ReportGroup;
+import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessTagModelDao;
+import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
+import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+
+public class BusinessTagFactory extends BusinessFactoryBase {
+
+    public BusinessTagFactory(final OSGIKillbillLogService logService,
+                              final OSGIKillbillAPI osgiKillbillAPI) {
+        super(logService, osgiKillbillAPI);
+    }
+
+    public Collection<BusinessTagModelDao> createBusinessTags(final UUID accountId,
+                                                              final CallContext context) throws AnalyticsRefreshException {
+        final Account account = getAccount(accountId, context);
+
+        final Long accountRecordId = getAccountRecordId(account.getId(), context);
+        final Long tenantRecordId = getTenantRecordId(context);
+        final ReportGroup reportGroup = getReportGroup(account.getId(), context);
+
+        final Collection<Tag> tags = getTagsForAccount(account.getId(), context);
+
+        final Collection<BusinessTagModelDao> tagModelDaos = new LinkedList<BusinessTagModelDao>();
+        for (final Tag tag : tags) {
+            final Long tagRecordId = getTagRecordId(tag.getId(), context);
+            final TagDefinition tagDefinition = getTagDefinition(tag.getTagDefinitionId(), context);
+            final AuditLog creationAuditLog = getTagCreationAuditLog(tag.getId(), context);
+            final BusinessTagModelDao tagModelDao = BusinessTagModelDao.create(account,
+                                                                               accountRecordId,
+                                                                               tag,
+                                                                               tagRecordId,
+                                                                               tagDefinition,
+                                                                               creationAuditLog,
+                                                                               tenantRecordId,
+                                                                               reportGroup);
+            tagModelDaos.add(tagModelDao);
+        }
+
+        return tagModelDaos;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
index 60f46e9..b13f3a7 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
@@ -125,6 +125,7 @@ public class TestBusinessInvoiceAndInvoicePaymentDao extends AnalyticsTestSuiteN
 
         // Setup the mocks
         // TODO this is really fragile - we need to extract a mock library for testing Kill Bill
+        Mockito.when(osgiKillbillApi.getAccountUserApi().getAccountById(account.getId(), callContext)).thenReturn(account);
         Mockito.when(osgiKillbillApi.getInvoiceUserApi().getInvoicesByAccount(account.getId(), callContext)).thenReturn(ImmutableList.<Invoice>of(invoice349, invoice570));
         Mockito.when(osgiKillbillApi.getInvoiceUserApi().getInvoice(invoice349Id, callContext)).thenReturn(invoice349);
         Mockito.when(osgiKillbillApi.getInvoiceUserApi().getInvoice(invoice570Id, callContext)).thenReturn(invoice570);
@@ -140,7 +141,7 @@ public class TestBusinessInvoiceAndInvoicePaymentDao extends AnalyticsTestSuiteN
         final Map<UUID, BusinessInvoiceModelDao> invoices = new HashMap<UUID, BusinessInvoiceModelDao>();
         final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
         final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments = new HashMap<UUID, Collection<BusinessInvoicePaymentBaseModelDao>>();
-        dao.createBusinessPojos(account, invoices, invoiceItems, invoicePayments, callContext);
+        dao.createBusinessPojos(account.getId(), invoices, invoiceItems, invoicePayments, callContext);
 
         /*
          * Expected Business invoice 349: