killbill-memoizeit

analytics: implement denormalized columns Signed-off-by:

4/19/2013 10:28:06 PM

Changes

Details

diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
index 2ded659..3a18bb0 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/AnalyticsListener.java
@@ -29,8 +29,7 @@ import com.ning.billing.commons.locker.GlobalLocker;
 import com.ning.billing.commons.locker.mysql.MySqlGlobalLocker;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessFieldDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceAndInvoicePaymentDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessTagDao;
@@ -50,8 +49,7 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
     private final LogService logService;
     private final BusinessAccountDao bacDao;
     private final BusinessSubscriptionTransitionDao bstDao;
-    private final BusinessInvoiceDao binDao;
-    private final BusinessInvoicePaymentDao bipDao;
+    private final BusinessInvoiceAndInvoicePaymentDao binAndBipDao;
     private final BusinessOverdueStatusDao bosDao;
     private final BusinessFieldDao bFieldDao;
     private final BusinessTagDao bTagDao;
@@ -64,8 +62,7 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
 
         this.bacDao = new BusinessAccountDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bstDao = new BusinessSubscriptionTransitionDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
-        this.binDao = new BusinessInvoiceDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
-        this.bipDao = new BusinessInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao, binDao);
+        this.binAndBipDao = new BusinessInvoiceAndInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
         this.bosDao = new BusinessOverdueStatusDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bFieldDao = new BusinessFieldDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bTagDao = new BusinessTagDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
@@ -135,7 +132,7 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
         updateWithAccountLock(killbillEvent, new Callable<Void>() {
             @Override
             public Void call() throws Exception {
-                binDao.update(killbillEvent.getAccountId(), callContext);
+                binAndBipDao.update(killbillEvent.getAccountId(), callContext);
                 return null;
             }
         });
@@ -145,7 +142,7 @@ public class AnalyticsListener implements OSGIKillbillEventHandler {
         updateWithAccountLock(killbillEvent, new Callable<Void>() {
             @Override
             public Void call() throws Exception {
-                bipDao.update(killbillEvent.getAccountId(), callContext);
+                binAndBipDao.update(killbillEvent.getAccountId(), callContext);
                 return null;
             }
         });
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java
index f3d573c..8826361 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/api/user/AnalyticsUserApi.java
@@ -35,8 +35,7 @@ import com.ning.billing.osgi.bundles.analytics.api.BusinessTag;
 import com.ning.billing.osgi.bundles.analytics.dao.AnalyticsDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessAccountDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessFieldDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceDao;
-import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoicePaymentDao;
+import com.ning.billing.osgi.bundles.analytics.dao.BusinessInvoiceAndInvoicePaymentDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessOverdueStatusDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessSubscriptionTransitionDao;
 import com.ning.billing.osgi.bundles.analytics.dao.BusinessTagDao;
@@ -51,8 +50,7 @@ public class AnalyticsUserApi extends BusinessAnalyticsBase {
     private final AnalyticsDao analyticsDao;
     private final BusinessAccountDao bacDao;
     private final BusinessSubscriptionTransitionDao bstDao;
-    private final BusinessInvoiceDao binDao;
-    private final BusinessInvoicePaymentDao bipDao;
+    private final BusinessInvoiceAndInvoicePaymentDao binAndBipDao;
     private final BusinessOverdueStatusDao bosDao;
     private final BusinessFieldDao bFieldDao;
     private final BusinessTagDao bTagDao;
@@ -64,8 +62,7 @@ public class AnalyticsUserApi extends BusinessAnalyticsBase {
         this.analyticsDao = new AnalyticsDao(osgiKillbillAPI, osgiKillbillDataSource);
         this.bacDao = new BusinessAccountDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bstDao = new BusinessSubscriptionTransitionDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
-        this.binDao = new BusinessInvoiceDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
-        this.bipDao = new BusinessInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao, binDao);
+        this.binAndBipDao = new BusinessInvoiceAndInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource, bacDao);
         this.bosDao = new BusinessOverdueStatusDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bFieldDao = new BusinessFieldDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
         this.bTagDao = new BusinessTagDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
@@ -103,8 +100,8 @@ public class AnalyticsUserApi extends BusinessAnalyticsBase {
     public void rebuildAnalyticsForAccount(final UUID accountId, final CallContext context) throws AnalyticsRefreshException {
         logService.log(LogService.LOG_INFO, "Starting rebuild of Analytics for account " + accountId);
 
-        // Refresh payments. This will automatically trigger a refresh of invoices and account
-        bipDao.update(accountId, context);
+        // Refresh invoices and payments. This will automatically trigger a refresh of account
+        binAndBipDao.update(accountId, context);
 
         // Refresh BST
         bstDao.update(accountId, context);
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
new file mode 100644
index 0000000..d41d613
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
@@ -0,0 +1,173 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+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.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.utils.BusinessInvoiceUtils;
+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;
+
+/**
+ * Wrapper around BusinessInvoiceDao and BusinessInvoicePaymentDao.
+ * <p/>
+ * These two should always be updated together as invoice and payment information is denormalized across tables.
+ */
+public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBase {
+
+    private final BusinessAccountDao businessAccountDao;
+    private final BusinessInvoiceDao businessInvoiceDao;
+    private final BusinessInvoicePaymentDao businessInvoicePaymentDao;
+
+    public BusinessInvoiceAndInvoicePaymentDao(final OSGIKillbillLogService logService,
+                                               final OSGIKillbillAPI osgiKillbillAPI,
+                                               final OSGIKillbillDataSource osgiKillbillDataSource,
+                                               final BusinessAccountDao businessAccountDao) {
+        super(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.businessAccountDao = businessAccountDao;
+        this.businessInvoiceDao = new BusinessInvoiceDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+        this.businessInvoicePaymentDao = new BusinessInvoicePaymentDao(logService, osgiKillbillAPI, osgiKillbillDataSource);
+    }
+
+    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 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);
+
+        // Delete and recreate invoice and invoice items in the transaction
+        sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
+            @Override
+            public Void inTransaction(final BusinessAnalyticsSqlDao transactional, final TransactionStatus status) throws Exception {
+                updateInTransaction(bac, invoices, invoiceItems, invoicePayments, transactional, context);
+                return null;
+            }
+        });
+    }
+
+    @VisibleForTesting
+    void createBusinessPojos(final Account account,
+                             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);
+
+        // Recompute all invoice payments (without denormalized payment fields populated)
+        final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments = businessInvoicePaymentDao.createBusinessInvoicePayments(account, businessInvoices, context);
+
+        // Transform the results
+        for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.keySet()) {
+            invoices.put(businessInvoice.getInvoiceId(), businessInvoice);
+            for (final BusinessInvoiceItemBaseModelDao businessInvoiceItem : businessInvoices.get(businessInvoice)) {
+                if (invoiceItems.get(businessInvoice.getInvoiceId()) == null) {
+                    invoiceItems.put(businessInvoice.getInvoiceId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
+                }
+                invoiceItems.get(businessInvoice.getInvoiceId()).add(businessInvoiceItem);
+            }
+        }
+        for (final BusinessInvoicePaymentBaseModelDao businessInvoicePayment : businessInvoicePayments) {
+            if (invoicePayments.get(businessInvoicePayment.getInvoiceId()) == null) {
+                invoicePayments.put(businessInvoicePayment.getInvoiceId(), new LinkedList<BusinessInvoicePaymentBaseModelDao>());
+            }
+            invoicePayments.get(businessInvoicePayment.getInvoiceId()).add(businessInvoicePayment);
+        }
+
+        // Populate missing fields
+        populatedMissingDenormalizedFields(invoices, invoiceItems, invoicePayments);
+    }
+
+    private void populatedMissingDenormalizedFields(final Map<UUID, BusinessInvoiceModelDao> businessInvoices,
+                                                    final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems,
+                                                    final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> businessInvoicePayments) {
+        // First, populated missing payment fields in invoice
+        for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.values()) {
+            final BigDecimal balance = BusinessInvoiceUtils.computeInvoiceBalance(businessInvoiceItems.get(businessInvoice.getInvoiceId()),
+                                                                                  businessInvoicePayments.get(businessInvoice.getInvoiceId()));
+            businessInvoice.setBalance(balance);
+
+            final BigDecimal amountPaid = BusinessInvoiceUtils.computeInvoiceAmountPaid(businessInvoicePayments.get(businessInvoice.getInvoiceId()));
+            businessInvoice.setAmountPaid(amountPaid);
+
+            final BigDecimal amountRefunded = BusinessInvoiceUtils.computeInvoiceAmountRefunded(businessInvoicePayments.get(businessInvoice.getInvoiceId()));
+            businessInvoice.setAmountRefunded(amountRefunded);
+        }
+
+        // At this point, all of the invoice objects are fully populated. Use them to update the invoice items and payment objects
+        for (final UUID invoiceId : businessInvoices.keySet()) {
+            final Collection<BusinessInvoiceItemBaseModelDao> invoiceItemsForInvoice = businessInvoiceItems.get(invoiceId);
+            if (invoiceItemsForInvoice != null) {
+                for (final BusinessInvoiceItemBaseModelDao businessInvoiceItem : invoiceItemsForInvoice) {
+                    businessInvoiceItem.populateDenormalizedInvoiceFields(businessInvoices.get(invoiceId));
+                }
+            }
+
+            final Collection<BusinessInvoicePaymentBaseModelDao> invoicePaymentsForInvoice = businessInvoicePayments.get(invoiceId);
+            if (invoicePaymentsForInvoice != null) {
+                for (final BusinessInvoicePaymentBaseModelDao businessInvoicePayment : invoicePaymentsForInvoice) {
+                    businessInvoicePayment.populateDenormalizedInvoiceFields(businessInvoices.get(invoiceId));
+                }
+            }
+        }
+    }
+
+    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
+        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);
+
+        // Update 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 3654cc8..ee396ab 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
@@ -19,9 +19,11 @@ 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;
@@ -29,8 +31,6 @@ import javax.annotation.Nullable;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.osgi.service.log.LogService;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.catalog.api.Currency;
@@ -46,6 +46,7 @@ import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBase
 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;
@@ -58,88 +59,102 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
 
-public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
+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;
 
-    private final BusinessAccountDao businessAccountDao;
+public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
 
     public BusinessInvoiceDao(final OSGIKillbillLogService logService,
                               final OSGIKillbillAPI osgiKillbillAPI,
-                              final OSGIKillbillDataSource osgiKillbillDataSource,
-                              final BusinessAccountDao businessAccountDao) {
+                              final OSGIKillbillDataSource osgiKillbillDataSource) {
         super(logService, osgiKillbillAPI, osgiKillbillDataSource);
-        this.businessAccountDao = businessAccountDao;
     }
 
-    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);
+    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
+    }
 
-        // Recompute all invoices and invoice items
-        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices = createBusinessInvoicesAndInvoiceItems(account, context);
+    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);
 
-        // Delete and recreate invoice and invoice items in the transaction
-        sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
-            @Override
-            public Void inTransaction(final BusinessAnalyticsSqlDao transactional, final TransactionStatus status) throws Exception {
-                updateInTransaction(bac, businessInvoices, transactional, context);
-                return null;
+        for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.values()) {
+            final Collection<BusinessInvoiceItemBaseModelDao> invoiceItems = businessInvoiceItems.get(businessInvoice.getInvoiceId());
+            if (invoiceItems != null) {
+                createInvoiceInTransaction(transactional, businessInvoice, invoiceItems, context);
             }
-        });
+        }
     }
 
-    public void updateInTransaction(final BusinessAccountModelDao bac,
-                                    final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
-                                    final BusinessAnalyticsSqlDao transactional,
-                                    final CallContext context) throws AnalyticsRefreshException {
-        rebuildInvoicesForAccountInTransaction(bac, businessInvoices, transactional, context);
+    private void deleteInvoicesAndInvoiceItemsForAccountInTransaction(final BusinessAnalyticsSqlDao transactional,
+                                                                      final Long accountRecordId,
+                                                                      final Long tenantRecordId,
+                                                                      final CallContext context) {
+        // Delete all invoice items
+        for (final String tableName : BusinessInvoiceItemBaseModelDao.ALL_INVOICE_ITEMS_TABLE_NAMES) {
+            transactional.deleteByAccountRecordId(tableName, accountRecordId, tenantRecordId, context);
+        }
 
-        // Update invoice and payment details in BAC
-        businessAccountDao.updateInTransaction(bac, transactional, context);
+        // Delete all invoices
+        transactional.deleteByAccountRecordId(BusinessInvoiceModelDao.INVOICES_TABLE_NAME, accountRecordId, tenantRecordId, context);
     }
 
+    private void createInvoiceInTransaction(final BusinessAnalyticsSqlDao transactional,
+                                            final BusinessInvoiceModelDao invoice,
+                                            final Iterable<BusinessInvoiceItemBaseModelDao> invoiceItems,
+                                            final CallContext context) {
+        // Create the invoice
+        transactional.create(invoice.getTableName(), invoice, context);
+
+        // Add associated invoice items
+        for (final BusinessInvoiceItemBaseModelDao invoiceItem : invoiceItems) {
+            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);
 
-        final Map<UUID, BusinessInvoiceModelDao> businessInvoices = new HashMap<UUID, BusinessInvoiceModelDao>();
-        final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
-
-        // All invoice items across all invoices for that account
-        // We need to be able to reference items across multiple invoices
-        final Collection<InvoiceItem> allInvoiceItems = new LinkedList<InvoiceItem>();
-
         // Lookup the invoices for that account
         final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
 
-        // Create a convenient mapping invoice_id -> invoice
+        // 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>();
-
-        // Create the business invoices
         for (final Invoice invoice : invoices) {
             invoiceIdToInvoiceMappings.put(invoice.getId(), invoice);
             allInvoiceItems.addAll(invoice.getInvoiceItems());
-
-            final Long invoiceRecordId = getInvoiceRecordId(invoice.getId(), context);
-            final AuditLog creationAuditLog = getInvoiceCreationAuditLog(invoice.getId(), context);
-            final BusinessInvoiceModelDao businessInvoice = new BusinessInvoiceModelDao(account,
-                                                                                        accountRecordId,
-                                                                                        invoice,
-                                                                                        invoiceRecordId,
-                                                                                        creationAuditLog,
-                                                                                        tenantRecordId,
-                                                                                        reportGroup);
-
-            businessInvoices.put(invoice.getId(), businessInvoice);
         }
 
         // Sanitize (cherry-pick, merge) the items
         final Collection<InvoiceItem> sanitizedInvoiceItems = sanitizeInvoiceItems(allInvoiceItems);
 
-        // Create the business invoice items
+        // 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,
@@ -157,59 +172,52 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                                                                                                   reportGroup,
                                                                                                   context);
             if (businessInvoiceItem != null) {
-                if (businessInvoiceItems.get(invoice.getId()) == null) {
-                    businessInvoiceItems.put(invoice.getId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
+                if (businessInvoiceItemsForInvoiceId.get(invoice.getId()) == null) {
+                    businessInvoiceItemsForInvoiceId.put(invoice.getId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
                 }
-                businessInvoiceItems.get(invoice.getId()).add(businessInvoiceItem);
+                businessInvoiceItemsForInvoiceId.get(invoice.getId()).add(businessInvoiceItem);
             }
         }
 
+        // Now, create the business invoices
         final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessRecords = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>>();
-        for (final BusinessInvoiceModelDao businessInvoiceModelDao : businessInvoices.values()) {
-            businessRecords.put(businessInvoiceModelDao, businessInvoiceItems.get(businessInvoiceModelDao.getInvoiceId()));
-        }
-
-        return businessRecords;
-    }
-
-    private void rebuildInvoicesForAccountInTransaction(final BusinessAccountModelDao account,
-                                                        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
-                                                        final BusinessAnalyticsSqlDao transactional,
-                                                        final CallContext context) {
-        deleteInvoicesAndInvoiceItemsForAccountInTransaction(transactional, account.getAccountRecordId(), account.getTenantRecordId(), context);
-
-        for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.keySet()) {
-            final Collection<BusinessInvoiceItemBaseModelDao> invoiceItems = businessInvoices.get(businessInvoice);
-            if (invoiceItems != null) {
-                createInvoiceInTransaction(transactional, businessInvoice, invoiceItems, context);
+        for (final Invoice invoice : invoices) {
+            final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems = businessInvoiceItemsForInvoiceId.get(invoice.getId());
+            if (businessInvoiceItems == null) {
+                continue;
             }
-        }
-    }
 
-    private void deleteInvoicesAndInvoiceItemsForAccountInTransaction(final BusinessAnalyticsSqlDao transactional,
-                                                                      final Long accountRecordId,
-                                                                      final Long tenantRecordId,
-                                                                      final CallContext context) {
-        // Delete all invoice items
-        for (final String tableName : BusinessInvoiceItemBaseModelDao.ALL_INVOICE_ITEMS_TABLE_NAMES) {
-            transactional.deleteByAccountRecordId(tableName, accountRecordId, tenantRecordId, context);
+            final BusinessInvoiceModelDao businessInvoice = createBusinessInvoice(account, invoice, businessInvoiceItems, accountRecordId, tenantRecordId, reportGroup, context);
+            businessRecords.put(businessInvoice, businessInvoiceItems);
         }
 
-        // Delete all invoices
-        transactional.deleteByAccountRecordId(BusinessInvoiceModelDao.INVOICES_TABLE_NAME, accountRecordId, tenantRecordId, context);
+        return businessRecords;
     }
 
-    private void createInvoiceInTransaction(final BusinessAnalyticsSqlDao transactional,
-                                            final BusinessInvoiceModelDao invoice,
-                                            final Iterable<BusinessInvoiceItemBaseModelDao> invoiceItems,
-                                            final CallContext context) {
-        // Create the invoice
-        transactional.create(invoice.getTableName(), invoice, context);
-
-        // Add associated invoice items
-        for (final BusinessInvoiceItemBaseModelDao invoiceItem : invoiceItems) {
-            transactional.create(invoiceItem.getTableName(), invoiceItem, context);
-        }
+    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,
@@ -307,46 +315,13 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                                                       reportGroup);
     }
 
-    private Boolean isRevenueRecognizable(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
-        // All items are recognizable except user generated credit (CBA_ADJ and CREDIT_ADJ on their own invoice)
-        return !(InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
-                 (otherInvoiceItems.size() == 1 &&
-                  InvoiceItemType.CREDIT_ADJ.equals(otherInvoiceItems.iterator().next().getInvoiceItemType()) &&
-                  otherInvoiceItems.iterator().next().getInvoiceId().equals(invoiceItem.getInvoiceId()) &&
-                  otherInvoiceItems.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
-    }
-
-    // Invoice adjustments
-    @VisibleForTesting
-    boolean isInvoiceAdjustmentItem(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
-        // Either REFUND_ADJ
-        return InvoiceItemType.REFUND_ADJ.equals(invoiceItem.getInvoiceItemType()) ||
-               // Or invoice level credit, i.e. credit adj, but NOT on its on own invoice
-               // Note: the negative credit adj items (internal generation of account level credits) doesn't figure in analytics
-               (InvoiceItemType.CREDIT_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
-                !(otherInvoiceItems.size() == 1 &&
-                  InvoiceItemType.CBA_ADJ.equals(otherInvoiceItems.iterator().next().getInvoiceItemType()) &&
-                  otherInvoiceItems.iterator().next().getInvoiceId().equals(invoiceItem.getInvoiceId()) &&
-                  otherInvoiceItems.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
-    }
-
-    // Item adjustments
-    private boolean isInvoiceItemAdjustmentItem(final InvoiceItem invoiceItem) {
-        return InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType());
-    }
-
-    // Account credits, gained or consumed
-    private boolean isAccountCreditItem(final InvoiceItem invoiceItem) {
-        return InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType());
-    }
-
-    // Regular line item (charges)
-    private boolean isCharge(final InvoiceItem invoiceItem) {
-        return InvoiceItemType.EXTERNAL_CHARGE.equals(invoiceItem.getInvoiceItemType()) ||
-               InvoiceItemType.FIXED.equals(invoiceItem.getInvoiceItemType()) ||
-               InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType());
-    }
-
+    /**
+     * 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)
@@ -390,10 +365,50 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
             }
         }
 
+        // 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 (InvoiceItemType.REPAIR_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+            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
@@ -404,10 +419,120 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
                 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;
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 5116b3b..423024c 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
@@ -19,10 +19,8 @@ package com.ning.billing.osgi.bundles.analytics.dao;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.Map;
-import java.util.UUID;
 
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
+import javax.annotation.Nullable;
 
 import com.ning.billing.account.api.Account;
 import com.ning.billing.invoice.api.Invoice;
@@ -44,50 +42,18 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
 
-    private final BusinessAccountDao businessAccountDao;
-    private final BusinessInvoiceDao businessInvoiceDao;
-
     public BusinessInvoicePaymentDao(final OSGIKillbillLogService logService,
                                      final OSGIKillbillAPI osgiKillbillAPI,
-                                     final OSGIKillbillDataSource osgiKillbillDataSource,
-                                     final BusinessAccountDao businessAccountDao,
-                                     final BusinessInvoiceDao businessInvoiceDao) {
+                                     final OSGIKillbillDataSource osgiKillbillDataSource) {
         super(logService, osgiKillbillAPI, osgiKillbillDataSource);
-        this.businessAccountDao = businessAccountDao;
-        this.businessInvoiceDao = businessInvoiceDao;
-    }
-
-    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);
-
-        // Recompute all invoice payments
-        final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments = createBusinessInvoicePayments(account, context);
-
-        // Recompute all invoice and invoice items
-        final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices = businessInvoiceDao.createBusinessInvoicesAndInvoiceItems(account, context);
-
-        sqlDao.inTransaction(new Transaction<Void, BusinessAnalyticsSqlDao>() {
-            @Override
-            public Void inTransaction(final BusinessAnalyticsSqlDao transactional, final TransactionStatus status) throws Exception {
-                updateInTransaction(bac, businessInvoices, businessInvoicePayments, transactional, context);
-                return null;
-            }
-        });
     }
 
-    private void updateInTransaction(final BusinessAccountModelDao bac,
-                                     final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoices,
-                                     final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments,
-                                     final BusinessAnalyticsSqlDao transactional,
-                                     final CallContext context) throws AnalyticsRefreshException {
+    public void updateInTransaction(final BusinessAccountModelDao bac,
+                                    final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments,
+                                    final BusinessAnalyticsSqlDao transactional,
+                                    final CallContext context) throws AnalyticsRefreshException {
         rebuildInvoicePaymentsForAccountInTransaction(bac, businessInvoicePayments, transactional, context);
-
-        // Update invoice balance details in BIN
-        // Note: no need to explicitly update BAC as well, since BusinessInvoiceDao will take care of it
-        businessInvoiceDao.updateInTransaction(bac, businessInvoices, transactional, context);
+        // Invoice and payment details in BAC will be updated by BusinessInvoiceAndInvoicePaymentDao
     }
 
     private void rebuildInvoicePaymentsForAccountInTransaction(final BusinessAccountModelDao bac,
@@ -103,8 +69,9 @@ public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
         }
     }
 
-    private Collection<BusinessInvoicePaymentBaseModelDao> createBusinessInvoicePayments(final Account account,
-                                                                                         final CallContext context) throws AnalyticsRefreshException {
+    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);
@@ -113,29 +80,13 @@ public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
 
         final Collection<InvoicePayment> invoicePayments = getAccountInvoicePayments(account.getId(), context);
         for (final InvoicePayment invoicePayment : invoicePayments) {
-            final Long invoicePaymentRecordId = getInvoicePaymentRecordId(invoicePayment.getId(), context);
-
-            final Invoice invoice = getInvoice(invoicePayment.getInvoiceId(), context);
-            final Payment payment = getPaymentWithPluginInfo(invoicePayment.getPaymentId(), context);
-            Refund refund = null;
-            if (invoicePayment.getPaymentCookieId() != null) {
-                refund = getRefundWithPluginInfo(invoicePayment.getPaymentCookieId(), context);
-            }
-
-            final PaymentMethod paymentMethod = getPaymentMethod(payment.getPaymentMethodId(), context);
-            final AuditLog creationAuditLog = getInvoicePaymentCreationAuditLog(invoicePayment.getId(), context);
-
-            final BusinessInvoicePaymentBaseModelDao businessInvoicePayment = BusinessInvoicePaymentBaseModelDao.create(account,
-                                                                                                                        accountRecordId,
-                                                                                                                        invoice,
-                                                                                                                        invoicePayment,
-                                                                                                                        invoicePaymentRecordId,
-                                                                                                                        payment,
-                                                                                                                        refund,
-                                                                                                                        paymentMethod,
-                                                                                                                        creationAuditLog,
-                                                                                                                        tenantRecordId,
-                                                                                                                        reportGroup);
+            final BusinessInvoicePaymentBaseModelDao businessInvoicePayment = createBusinessInvoicePayment(account,
+                                                                                                           invoicePayment,
+                                                                                                           businessInvoices,
+                                                                                                           accountRecordId,
+                                                                                                           tenantRecordId,
+                                                                                                           reportGroup,
+                                                                                                           context);
             if (businessInvoicePayment != null) {
                 businessInvoicePayments.add(businessInvoicePayment);
             }
@@ -143,4 +94,36 @@ public class BusinessInvoicePaymentDao extends BusinessAnalyticsDaoBase {
 
         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/model/BusinessInvoiceAdjustmentModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java
index 8c64231..94d3063 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceAdjustmentModelDao.java
@@ -62,4 +62,9 @@ public class BusinessInvoiceAdjustmentModelDao extends BusinessInvoiceItemBaseMo
     public String getTableName() {
         return INVOICE_ADJUSTMENTS_TABLE_NAME;
     }
+
+    @Override
+    public BusinessInvoiceItemType getBusinessInvoiceItemType() {
+        return BusinessInvoiceItemType.INVOICE_ADJUSTMENT;
+    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java
index fca1e6a..4a0e3f9 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemAdjustmentModelDao.java
@@ -62,4 +62,9 @@ public class BusinessInvoiceItemAdjustmentModelDao extends BusinessInvoiceItemBa
     public String getTableName() {
         return INVOICE_ITEM_ADJUSTMENTS_TABLE_NAME;
     }
+
+    @Override
+    public BusinessInvoiceItemType getBusinessInvoiceItemType() {
+        return BusinessInvoiceItemType.INVOICE_ITEM_ADJUSTMENT;
+    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java
index f0fcef2..75e5643 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemBaseModelDao.java
@@ -55,6 +55,7 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
     private BigDecimal invoiceAmountCharged;
     private BigDecimal invoiceOriginalAmountCharged;
     private BigDecimal invoiceAmountCredited;
+    private BigDecimal invoiceAmountRefunded;
     private String itemType;
     private Boolean revenueRecognizable;
     private String bundleExternalKey;
@@ -77,6 +78,8 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
         CHARGE
     }
 
+    public abstract BusinessInvoiceItemType getBusinessInvoiceItemType();
+
     public static BusinessInvoiceItemBaseModelDao create(final Account account,
                                                          final Long accountRecordId,
                                                          final Invoice invoice,
@@ -164,11 +167,6 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
                                            final LocalDate invoiceDate,
                                            final LocalDate invoiceTargetDate,
                                            final String invoiceCurrency,
-                                           final BigDecimal invoiceBalance,
-                                           final BigDecimal invoiceAmountPaid,
-                                           final BigDecimal invoiceAmountCharged,
-                                           final BigDecimal invoiceOriginalAmountCharged,
-                                           final BigDecimal invoiceAmountCredited,
                                            final String itemType,
                                            final Boolean revenueRecognizable,
                                            final String bundleExternalKey,
@@ -212,11 +210,6 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
         this.invoiceDate = invoiceDate;
         this.invoiceTargetDate = invoiceTargetDate;
         this.invoiceCurrency = invoiceCurrency;
-        this.invoiceBalance = invoiceBalance;
-        this.invoiceAmountPaid = invoiceAmountPaid;
-        this.invoiceAmountCharged = invoiceAmountCharged;
-        this.invoiceOriginalAmountCharged = invoiceOriginalAmountCharged;
-        this.invoiceAmountCredited = invoiceAmountCredited;
         this.itemType = itemType;
         this.revenueRecognizable = revenueRecognizable;
         this.bundleExternalKey = bundleExternalKey;
@@ -255,23 +248,18 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
              invoice.getInvoiceDate(),
              invoice.getTargetDate(),
              invoice.getCurrency() == null ? null : invoice.getCurrency().toString(),
-             invoice.getBalance(),
-             invoice.getPaidAmount(),
-             invoice.getChargedAmount(),
-             invoice.getOriginalChargedAmount(),
-             invoice.getCreditAdjAmount(),
              invoiceItem.getInvoiceItemType().toString(),
              revenueRecognizable,
              bundle == null ? null : bundle.getExternalKey(),
-             plan != null ? plan.getProduct().getName() : null,
-             plan != null ? plan.getProduct().getCatalogName() : null,
-             plan != null ? plan.getProduct().getCategory().toString() : null,
+             (plan != null && plan.getProduct() != null) ? plan.getProduct().getName() : null,
+             (plan != null && plan.getProduct() != null) ? plan.getProduct().getCatalogName() : null,
+             (plan != null && plan.getProduct().getCategory() != null) ? plan.getProduct().getCategory().toString() : null,
              planPhase != null ? planPhase.getName() : null,
-             planPhase != null ? planPhase.getPhaseType().toString() : null,
-             planPhase != null ? planPhase.getBillingPeriod().toString() : null,
+             (planPhase != null && planPhase.getPhaseType() != null) ? planPhase.getPhaseType().toString() : null,
+             (planPhase != null && planPhase.getBillingPeriod() != null) ? planPhase.getBillingPeriod().toString() : null,
              invoiceItem.getStartDate(),
              /* Populate end date for fixed items for convenience (null in invoice_items table) */
-             (invoiceItem.getEndDate() == null && planPhase != null) ? invoiceItem.getStartDate().plus(planPhase.getDuration().toJodaPeriod()) : invoiceItem.getEndDate(),
+             (invoiceItem.getEndDate() == null && planPhase != null && planPhase.getDuration() != null) ? invoiceItem.getStartDate().plus(planPhase.getDuration().toJodaPeriod()) : invoiceItem.getEndDate(),
              invoiceItem.getAmount(),
              invoiceItem.getCurrency() == null ? null : invoiceItem.getCurrency().toString(),
              invoiceItem.getLinkedItemId(),
@@ -287,6 +275,15 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
              reportGroup);
     }
 
+    public void populateDenormalizedInvoiceFields(final BusinessInvoiceModelDao businessInvoice) {
+        invoiceBalance = businessInvoice.getBalance();
+        invoiceAmountPaid = businessInvoice.getAmountPaid();
+        invoiceAmountCharged = businessInvoice.getAmountCharged();
+        invoiceOriginalAmountCharged = businessInvoice.getOriginalAmountCharged();
+        invoiceAmountCredited = businessInvoice.getAmountCredited();
+        invoiceAmountRefunded = businessInvoice.getAmountRefunded();
+    }
+
     public Long getInvoiceItemRecordId() {
         return invoiceItemRecordId;
     }
@@ -343,6 +340,10 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
         return invoiceAmountCredited;
     }
 
+    public BigDecimal getInvoiceAmountRefunded() {
+        return invoiceAmountRefunded;
+    }
+
     public String getItemType() {
         return itemType;
     }
@@ -417,6 +418,7 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
         sb.append(", invoiceAmountCharged=").append(invoiceAmountCharged);
         sb.append(", invoiceOriginalAmountCharged=").append(invoiceOriginalAmountCharged);
         sb.append(", invoiceAmountCredited=").append(invoiceAmountCredited);
+        sb.append(", invoiceAmountRefunded=").append(invoiceAmountRefunded);
         sb.append(", itemType='").append(itemType).append('\'');
         sb.append(", revenueRecognizable=").append(revenueRecognizable);
         sb.append(", bundleExternalKey='").append(bundleExternalKey).append('\'');
@@ -473,6 +475,9 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
         if (invoiceAmountPaid != null ? (invoiceAmountPaid.compareTo(that.invoiceAmountPaid) != 0) : that.invoiceAmountPaid != null) {
             return false;
         }
+        if (invoiceAmountRefunded != null ? (invoiceAmountRefunded.compareTo(that.invoiceAmountRefunded) != 0) : that.invoiceAmountRefunded != null) {
+            return false;
+        }
         if (invoiceBalance != null ? (invoiceBalance.compareTo(that.invoiceBalance) != 0) : that.invoiceBalance != null) {
             return false;
         }
@@ -554,6 +559,7 @@ public abstract class BusinessInvoiceItemBaseModelDao extends BusinessModelDaoBa
         result = 31 * result + (invoiceAmountCharged != null ? invoiceAmountCharged.hashCode() : 0);
         result = 31 * result + (invoiceOriginalAmountCharged != null ? invoiceOriginalAmountCharged.hashCode() : 0);
         result = 31 * result + (invoiceAmountCredited != null ? invoiceAmountCredited.hashCode() : 0);
+        result = 31 * result + (invoiceAmountRefunded != null ? invoiceAmountRefunded.hashCode() : 0);
         result = 31 * result + (itemType != null ? itemType.hashCode() : 0);
         result = 31 * result + (revenueRecognizable != null ? revenueRecognizable.hashCode() : 0);
         result = 31 * result + (bundleExternalKey != null ? bundleExternalKey.hashCode() : 0);
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java
index f9557d2..b299b30 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemCreditModelDao.java
@@ -62,4 +62,9 @@ public class BusinessInvoiceItemCreditModelDao extends BusinessInvoiceItemBaseMo
     public String getTableName() {
         return ACCOUNT_CREDITS_TABLE_NAME;
     }
+
+    @Override
+    public BusinessInvoiceItemType getBusinessInvoiceItemType() {
+        return BusinessInvoiceItemType.ACCOUNT_CREDIT;
+    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java
index bd7832c..753f979 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceItemModelDao.java
@@ -62,4 +62,9 @@ public class BusinessInvoiceItemModelDao extends BusinessInvoiceItemBaseModelDao
     public String getTableName() {
         return INVOICE_ITEMS_TABLE_NAME;
     }
+
+    @Override
+    public BusinessInvoiceItemType getBusinessInvoiceItemType() {
+        return BusinessInvoiceItemType.CHARGE;
+    }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceModelDao.java
index 650a86c..6aa8a9f 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoiceModelDao.java
@@ -43,6 +43,7 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
     private BigDecimal amountCharged;
     private BigDecimal originalAmountCharged;
     private BigDecimal amountCredited;
+    private BigDecimal amountRefunded;
 
     public BusinessInvoiceModelDao() { /* When reading from the database */ }
 
@@ -52,8 +53,6 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
                                    final LocalDate invoiceDate,
                                    final LocalDate targetDate,
                                    final String currency,
-                                   final BigDecimal balance,
-                                   final BigDecimal amountPaid,
                                    final BigDecimal amountCharged,
                                    final BigDecimal originalAmountCharged,
                                    final BigDecimal amountCredited,
@@ -83,8 +82,6 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
         this.invoiceDate = invoiceDate;
         this.targetDate = targetDate;
         this.currency = currency;
-        this.balance = balance;
-        this.amountPaid = amountPaid;
         this.amountCharged = amountCharged;
         this.originalAmountCharged = originalAmountCharged;
         this.amountCredited = amountCredited;
@@ -93,6 +90,9 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
     public BusinessInvoiceModelDao(final Account account,
                                    final Long accountRecordId,
                                    final Invoice invoice,
+                                   final BigDecimal amountCharged,
+                                   final BigDecimal originalAmountCharged,
+                                   final BigDecimal amountCredited,
                                    final Long invoiceRecordId,
                                    @Nullable final AuditLog creationAuditLog,
                                    final Long tenantRecordId,
@@ -103,11 +103,9 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
              invoice.getInvoiceDate(),
              invoice.getTargetDate(),
              invoice.getCurrency() == null ? null : invoice.getCurrency().toString(),
-             invoice.getBalance(),
-             invoice.getPaidAmount(),
-             invoice.getChargedAmount(),
-             invoice.getOriginalChargedAmount(),
-             invoice.getCreditAdjAmount(),
+             amountCharged,
+             originalAmountCharged,
+             amountCredited,
              invoice.getCreatedDate(),
              creationAuditLog != null ? creationAuditLog.getUserName() : null,
              creationAuditLog != null ? creationAuditLog.getReasonCode() : null,
@@ -120,6 +118,21 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
              reportGroup);
     }
 
+    // Denormalized payment field
+    public void setBalance(final BigDecimal balance) {
+        this.balance = balance;
+    }
+
+    // Denormalized payment field
+    public void setAmountPaid(final BigDecimal amountPaid) {
+        this.amountPaid = amountPaid;
+    }
+
+    // Denormalized payment field
+    public void setAmountRefunded(final BigDecimal amountRefunded) {
+        this.amountRefunded = amountRefunded;
+    }
+
     @Override
     public String getTableName() {
         return INVOICES_TABLE_NAME;
@@ -169,6 +182,10 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
         return amountCredited;
     }
 
+    public BigDecimal getAmountRefunded() {
+        return amountRefunded;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -184,6 +201,7 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
         sb.append(", amountCharged=").append(amountCharged);
         sb.append(", originalAmountCharged=").append(originalAmountCharged);
         sb.append(", amountCredited=").append(amountCredited);
+        sb.append(", amountRefunded=").append(amountRefunded);
         sb.append('}');
         return sb.toString();
     }
@@ -211,6 +229,9 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
         if (amountPaid != null ? (amountPaid.compareTo(that.amountPaid) != 0) : that.amountPaid != null) {
             return false;
         }
+        if (amountRefunded != null ? (amountRefunded.compareTo(that.amountRefunded) != 0) : that.amountRefunded != null) {
+            return false;
+        }
         if (balance != null ? (balance.compareTo(that.balance) != 0) : that.balance != null) {
             return false;
         }
@@ -253,6 +274,7 @@ public class BusinessInvoiceModelDao extends BusinessModelDaoBase {
         result = 31 * result + (amountCharged != null ? amountCharged.hashCode() : 0);
         result = 31 * result + (originalAmountCharged != null ? originalAmountCharged.hashCode() : 0);
         result = 31 * result + (amountCredited != null ? amountCredited.hashCode() : 0);
+        result = 31 * result + (amountRefunded != null ? amountRefunded.hashCode() : 0);
         return result;
     }
 }
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoicePaymentBaseModelDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoicePaymentBaseModelDao.java
index 402c98d..b542755 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoicePaymentBaseModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/model/BusinessInvoicePaymentBaseModelDao.java
@@ -59,6 +59,7 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
     private BigDecimal invoiceAmountCharged;
     private BigDecimal invoiceOriginalAmountCharged;
     private BigDecimal invoiceAmountCredited;
+    private BigDecimal invoiceAmountRefunded;
     private String invoicePaymentType;
     private UUID paymentId;
     private UUID refundId;
@@ -149,11 +150,6 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
                                               final LocalDate invoiceDate,
                                               final LocalDate invoiceTargetDate,
                                               final String invoiceCurrency,
-                                              final BigDecimal invoiceBalance,
-                                              final BigDecimal invoiceAmountPaid,
-                                              final BigDecimal invoiceAmountCharged,
-                                              final BigDecimal invoiceOriginalAmountCharged,
-                                              final BigDecimal invoiceAmountCredited,
                                               final String invoicePaymentType,
                                               final UUID paymentId,
                                               final UUID refundId,
@@ -211,11 +207,6 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
         this.invoiceDate = invoiceDate;
         this.invoiceTargetDate = invoiceTargetDate;
         this.invoiceCurrency = invoiceCurrency;
-        this.invoiceBalance = invoiceBalance;
-        this.invoiceAmountPaid = invoiceAmountPaid;
-        this.invoiceAmountCharged = invoiceAmountCharged;
-        this.invoiceOriginalAmountCharged = invoiceOriginalAmountCharged;
-        this.invoiceAmountCredited = invoiceAmountCredited;
         this.invoicePaymentType = invoicePaymentType;
         this.paymentId = paymentId;
         this.refundId = refundId;
@@ -266,12 +257,7 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
              invoice.getInvoiceDate(),
              invoice.getTargetDate(),
              invoice.getCurrency() == null ? null : invoice.getCurrency().toString(),
-             invoice.getBalance(),
-             invoice.getPaidAmount(),
-             invoice.getChargedAmount(),
-             invoice.getOriginalChargedAmount(),
-             invoice.getCreditAdjAmount(),
-             invoicePayment.getType().toString(),
+             invoicePayment.getType() == null ? null : invoicePayment.getType().toString(),
              payment.getId(),
              refund != null ? refund.getId() : null,
              payment.getPaymentNumber() == null ? null : payment.getPaymentNumber().longValue(),
@@ -281,7 +267,7 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
              paymentMethod != null ? paymentMethod.getPluginName() : DEFAULT_PLUGIN_NAME,
              refund != null ? (refund.getPluginDetail() != null ? refund.getPluginDetail().getCreatedDate() : null) : (payment.getPaymentInfoPlugin() != null ? payment.getPaymentInfoPlugin().getCreatedDate() : null),
              refund != null ? (refund.getPluginDetail() != null ? refund.getPluginDetail().getEffectiveDate() : null) : (payment.getPaymentInfoPlugin() != null ? payment.getPaymentInfoPlugin().getEffectiveDate() : null),
-             refund != null ? (refund.getPluginDetail() != null ? refund.getPluginDetail().getStatus().toString() : null) : (payment.getPaymentInfoPlugin() != null ? payment.getPaymentInfoPlugin().getStatus().toString() : null),
+             refund != null ? (refund.getPluginDetail() != null && refund.getPluginDetail().getStatus() != null ? refund.getPluginDetail().getStatus().toString() : null) : (payment.getPaymentInfoPlugin() != null && payment.getPaymentInfoPlugin().getStatus() != null ? payment.getPaymentInfoPlugin().getStatus().toString() : null),
              refund != null ? (refund.getPluginDetail() != null ? refund.getPluginDetail().getGatewayError() : null) : (payment.getPaymentInfoPlugin() != null ? payment.getPaymentInfoPlugin().getGatewayError() : null),
              refund != null ? (refund.getPluginDetail() != null ? refund.getPluginDetail().getGatewayErrorCode() : null) : (payment.getPaymentInfoPlugin() != null ? payment.getPaymentInfoPlugin().getGatewayErrorCode() : null),
              refund != null ? (refund.getPluginDetail() != null ? refund.getPluginDetail().getReferenceId() : null) : (payment.getPaymentInfoPlugin() != null ? payment.getPaymentInfoPlugin().getFirstPaymentReferenceId() : null),
@@ -312,6 +298,15 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
              reportGroup);
     }
 
+    public void populateDenormalizedInvoiceFields(final BusinessInvoiceModelDao businessInvoice) {
+        invoiceBalance = businessInvoice.getBalance();
+        invoiceAmountPaid = businessInvoice.getAmountPaid();
+        invoiceAmountCharged = businessInvoice.getAmountCharged();
+        invoiceOriginalAmountCharged = businessInvoice.getOriginalAmountCharged();
+        invoiceAmountCredited = businessInvoice.getAmountCredited();
+        invoiceAmountRefunded = businessInvoice.getAmountRefunded();
+    }
+
     public Long getInvoicePaymentRecordId() {
         return invoicePaymentRecordId;
     }
@@ -364,6 +359,10 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
         return invoiceAmountCredited;
     }
 
+    public BigDecimal getInvoiceAmountRefunded() {
+        return invoiceAmountRefunded;
+    }
+
     public String getInvoicePaymentType() {
         return invoicePaymentType;
     }
@@ -496,6 +495,7 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
         sb.append(", invoiceAmountCharged=").append(invoiceAmountCharged);
         sb.append(", invoiceOriginalAmountCharged=").append(invoiceOriginalAmountCharged);
         sb.append(", invoiceAmountCredited=").append(invoiceAmountCredited);
+        sb.append(", invoiceAmountRefunded=").append(invoiceAmountRefunded);
         sb.append(", invoicePaymentType='").append(invoicePaymentType).append('\'');
         sb.append(", paymentId=").append(paymentId);
         sb.append(", refundId=").append(refundId);
@@ -558,6 +558,9 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
         if (invoiceAmountPaid != null ? (invoiceAmountPaid.compareTo(that.invoiceAmountPaid) != 0) : that.invoiceAmountPaid != null) {
             return false;
         }
+        if (invoiceAmountRefunded != null ? (invoiceAmountRefunded.compareTo(that.invoiceAmountRefunded) != 0) : that.invoiceAmountRefunded != null) {
+            return false;
+        }
         if (invoiceBalance != null ? (invoiceBalance.compareTo(that.invoiceBalance) != 0) : that.invoiceBalance != null) {
             return false;
         }
@@ -689,6 +692,7 @@ public abstract class BusinessInvoicePaymentBaseModelDao extends BusinessModelDa
         result = 31 * result + (invoiceAmountCharged != null ? invoiceAmountCharged.hashCode() : 0);
         result = 31 * result + (invoiceOriginalAmountCharged != null ? invoiceOriginalAmountCharged.hashCode() : 0);
         result = 31 * result + (invoiceAmountCredited != null ? invoiceAmountCredited.hashCode() : 0);
+        result = 31 * result + (invoiceAmountRefunded != null ? invoiceAmountRefunded.hashCode() : 0);
         result = 31 * result + (invoicePaymentType != null ? invoicePaymentType.hashCode() : 0);
         result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
         result = 31 * result + (refundId != null ? refundId.hashCode() : 0);
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java
new file mode 100644
index 0000000..40ef30b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java
@@ -0,0 +1,155 @@
+/*
+ * 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.utils;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.InvoicePayment.InvoicePaymentType;
+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.BusinessInvoicePaymentBaseModelDao;
+
+public class BusinessInvoiceUtils {
+
+    public static Boolean isRevenueRecognizable(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
+        // All items are recognizable except user generated credit (CBA_ADJ and CREDIT_ADJ on their own invoice)
+        return !(InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
+                 (otherInvoiceItems.size() == 1 &&
+                  InvoiceItemType.CREDIT_ADJ.equals(otherInvoiceItems.iterator().next().getInvoiceItemType()) &&
+                  otherInvoiceItems.iterator().next().getInvoiceId().equals(invoiceItem.getInvoiceId()) &&
+                  otherInvoiceItems.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
+    }
+
+    // Invoice adjustments
+    public static boolean isInvoiceAdjustmentItem(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
+        // Either REFUND_ADJ
+        return InvoiceItemType.REFUND_ADJ.equals(invoiceItem.getInvoiceItemType()) ||
+               // Or invoice level credit, i.e. credit adj, but NOT on its on own invoice
+               // Note: the negative credit adj items (internal generation of account level credits) doesn't figure in analytics
+               (InvoiceItemType.CREDIT_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
+                !(otherInvoiceItems.size() == 1 &&
+                  InvoiceItemType.CBA_ADJ.equals(otherInvoiceItems.iterator().next().getInvoiceItemType()) &&
+                  otherInvoiceItems.iterator().next().getInvoiceId().equals(invoiceItem.getInvoiceId()) &&
+                  otherInvoiceItems.iterator().next().getAmount().compareTo(invoiceItem.getAmount().negate()) == 0));
+    }
+
+    // Item adjustments
+    public static boolean isInvoiceItemAdjustmentItem(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.ITEM_ADJ.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    // Account credits, gained or consumed
+    public static boolean isAccountCreditItem(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    // Regular line item (charges)
+    public static boolean isCharge(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.EXTERNAL_CHARGE.equals(invoiceItem.getInvoiceItemType()) ||
+               InvoiceItemType.FIXED.equals(invoiceItem.getInvoiceItemType()) ||
+               InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType());
+    }
+
+    public static BigDecimal computeInvoiceBalance(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
+                                                   @Nullable final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
+        return computeInvoiceAmountCharged(businessInvoiceItems)
+                .add(computeInvoiceAmountCredited(businessInvoiceItems))
+                .add(
+                        computeInvoiceAmountPaid(businessInvoicePayments).negate()
+                                .add(computeInvoiceAmountRefunded(businessInvoicePayments).negate())
+                    );
+    }
+
+    public static BigDecimal computeInvoiceAmountCharged(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
+        BigDecimal amountCharged = BigDecimal.ZERO;
+        if (businessInvoiceItems == null) {
+            return amountCharged;
+        }
+
+        for (final BusinessInvoiceItemBaseModelDao businessInvoiceItem : businessInvoiceItems) {
+            if (BusinessInvoiceItemType.CHARGE.equals(businessInvoiceItem.getBusinessInvoiceItemType()) ||
+                BusinessInvoiceItemType.INVOICE_ADJUSTMENT.equals(businessInvoiceItem.getBusinessInvoiceItemType()) ||
+                BusinessInvoiceItemType.INVOICE_ITEM_ADJUSTMENT.equals(businessInvoiceItem.getBusinessInvoiceItemType())) {
+                amountCharged = amountCharged.add(businessInvoiceItem.getAmount());
+            }
+        }
+        return amountCharged;
+    }
+
+    public static BigDecimal computeInvoiceOriginalAmountCharged(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
+        BigDecimal amountCharged = BigDecimal.ZERO;
+        if (businessInvoiceItems == null) {
+            return amountCharged;
+        }
+
+        for (final BusinessInvoiceItemBaseModelDao businessInvoiceItem : businessInvoiceItems) {
+            if (BusinessInvoiceItemType.CHARGE.equals(businessInvoiceItem.getBusinessInvoiceItemType()) &&
+                businessInvoiceItem.getCreatedDate().equals(businessInvoiceItem.getInvoiceCreatedDate())) {
+                amountCharged = amountCharged.add(businessInvoiceItem.getAmount());
+            }
+        }
+        return amountCharged;
+    }
+
+    public static BigDecimal computeInvoiceAmountCredited(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
+        BigDecimal amountCredited = BigDecimal.ZERO;
+        if (businessInvoiceItems == null) {
+            return amountCredited;
+        }
+
+        for (final BusinessInvoiceItemBaseModelDao businessInvoiceItem : businessInvoiceItems) {
+            if (BusinessInvoiceItemType.ACCOUNT_CREDIT.equals(businessInvoiceItem.getBusinessInvoiceItemType())) {
+                amountCredited = amountCredited.add(businessInvoiceItem.getAmount());
+            }
+        }
+        return amountCredited;
+    }
+
+    public static BigDecimal computeInvoiceAmountPaid(@Nullable final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
+        BigDecimal amountPaid = BigDecimal.ZERO;
+        if (businessInvoicePayments == null) {
+            return amountPaid;
+        }
+
+        for (final BusinessInvoicePaymentBaseModelDao businessInvoicePayment : businessInvoicePayments) {
+            if (InvoicePaymentType.ATTEMPT.toString().equals(businessInvoicePayment.getInvoicePaymentType())) {
+                amountPaid = amountPaid.add(businessInvoicePayment.getAmount());
+            }
+        }
+        return amountPaid;
+    }
+
+    public static BigDecimal computeInvoiceAmountRefunded(@Nullable final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
+        BigDecimal amountRefunded = BigDecimal.ZERO;
+        if (businessInvoicePayments == null) {
+            return amountRefunded;
+        }
+
+        for (final BusinessInvoicePaymentBaseModelDao businessInvoicePayment : businessInvoicePayments) {
+            if (InvoicePaymentType.REFUND.toString().equals(businessInvoicePayment.getInvoicePaymentType()) ||
+                InvoicePaymentType.CHARGED_BACK.toString().equals(businessInvoicePayment.getInvoicePaymentType())) {
+                amountRefunded = amountRefunded.add(businessInvoicePayment.getAmount());
+            }
+        }
+        return amountRefunded;
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg
index 4dc7879..11fa694 100644
--- a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/dao/BusinessAnalyticsSqlDao.sql.stg
@@ -241,6 +241,7 @@ insert into bin (
 , amount_charged
 , original_amount_charged
 , amount_credited
+, amount_refunded
 , created_date
 , created_by
 , created_reason_code
@@ -263,6 +264,7 @@ insert into bin (
 , :amountCharged
 , :originalAmountCharged
 , :amountCredited
+, :amountRefunded
 , :createdDate
 , :createdBy
 , :createdReasonCode
@@ -292,6 +294,7 @@ insert into bia (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , item_type
 , revenue_recognizable
 , bundle_external_key
@@ -331,6 +334,7 @@ insert into bia (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :itemType
 , :revenueRecognizable
 , :bundleExternalKey
@@ -374,6 +378,7 @@ insert into bii (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , item_type
 , revenue_recognizable
 , bundle_external_key
@@ -413,6 +418,7 @@ insert into bii (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :itemType
 , :revenueRecognizable
 , :bundleExternalKey
@@ -456,6 +462,7 @@ insert into biia (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , item_type
 , revenue_recognizable
 , bundle_external_key
@@ -495,6 +502,7 @@ insert into biia (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :itemType
 , :revenueRecognizable
 , :bundleExternalKey
@@ -538,6 +546,7 @@ insert into biic (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , item_type
 , revenue_recognizable
 , bundle_external_key
@@ -577,6 +586,7 @@ insert into biic (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :itemType
 , :revenueRecognizable
 , :bundleExternalKey
@@ -619,6 +629,7 @@ insert into bip (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , invoice_payment_type
 , payment_id
 , payment_number
@@ -671,6 +682,7 @@ insert into bip (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :invoicePaymentType
 , :paymentId
 , :paymentNumber
@@ -727,6 +739,7 @@ insert into bipr (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , invoice_payment_type
 , payment_id
 , refund_id
@@ -780,6 +793,7 @@ insert into bipr (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :invoicePaymentType
 , :paymentId
 , :refundId
@@ -837,6 +851,7 @@ insert into bipc (
 , invoice_amount_charged
 , invoice_original_amount_charged
 , invoice_amount_credited
+, invoice_amount_refunded
 , invoice_payment_type
 , payment_id
 , payment_number
@@ -889,6 +904,7 @@ insert into bipc (
 , :invoiceAmountCharged
 , :invoiceOriginalAmountCharged
 , :invoiceAmountCredited
+, :invoiceAmountRefunded
 , :invoicePaymentType
 , :paymentId
 , :paymentNumber
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
index 7eca803..5a57ac6 100644
--- a/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
+++ b/osgi-bundles/bundles/analytics/src/main/resources/com/ning/billing/osgi/bundles/analytics/ddl.sql
@@ -154,6 +154,7 @@ create table bin (
 , amount_charged numeric(10, 4) default 0
 , original_amount_charged numeric(10, 4) default 0
 , amount_credited numeric(10, 4) default 0
+, amount_refunded numeric(10, 4) default 0
 , created_date datetime default null
 , created_by varchar(50) default null
 , created_reason_code varchar(255) default null
@@ -190,6 +191,7 @@ create table bia (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , item_type char(50) default null
 , revenue_recognizable bool default true
 , bundle_external_key varchar(50) default null
@@ -240,6 +242,7 @@ create table bii (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , item_type char(50) default null
 , revenue_recognizable bool default true
 , bundle_external_key varchar(50) default null
@@ -290,6 +293,7 @@ create table biia (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , item_type char(50) default null
 , revenue_recognizable bool default true
 , bundle_external_key varchar(50) default null
@@ -340,6 +344,7 @@ create table biic (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , item_type char(50) default null
 , revenue_recognizable bool default true
 , bundle_external_key varchar(50) default null
@@ -389,6 +394,7 @@ create table bip (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , invoice_payment_type varchar(50) default null
 , payment_id char(36) default null
 , payment_number bigint default null
@@ -452,6 +458,7 @@ create table bipr (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , invoice_payment_type varchar(50) default null
 , payment_id char(36) default null
 , refund_id char(36) default null
@@ -516,6 +523,7 @@ create table bipc (
 , invoice_amount_charged numeric(10, 4) default 0
 , invoice_original_amount_charged numeric(10, 4) default 0
 , invoice_amount_credited numeric(10, 4) default 0
+, invoice_amount_refunded numeric(10, 4) default 0
 , invoice_payment_type varchar(50) default null
 , payment_id char(36) default null
 , payment_number bigint default null
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
index 3b336e4..9e079bd 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/AnalyticsTestSuiteNoDB.java
@@ -17,8 +17,11 @@
 package com.ning.billing.osgi.bundles.analytics;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
@@ -75,6 +78,7 @@ import com.google.common.collect.ImmutableList;
 
 public abstract class AnalyticsTestSuiteNoDB {
 
+    private static final DateTime INVOICE_CREATED_DATE = new DateTime(2016, 1, 22, 10, 56, 53, DateTimeZone.UTC);
     protected final Logger logger = LoggerFactory.getLogger(AnalyticsTestSuiteNoDB.class);
 
     protected final Long accountRecordId = 1L;
@@ -140,6 +144,77 @@ public abstract class AnalyticsTestSuiteNoDB {
         Assert.assertEquals(businessModelDaoBase.getReportGroup(), reportGroup.toString());
     }
 
+    protected Invoice createInvoice(final UUID invoiceId, final Integer invoiceNumber, final List<InvoiceItem> items) {
+        final Invoice invoice = Mockito.mock(Invoice.class);
+
+        Mockito.when(invoice.getId()).thenReturn(invoiceId);
+        Mockito.when(invoice.getNumberOfItems()).thenReturn(items.size());
+        Mockito.when(invoice.getInvoiceItems()).thenReturn(items);
+        Mockito.when(invoice.getNumberOfPayments()).thenReturn(0);
+        Mockito.when(invoice.getAccountId()).thenReturn(UUID.randomUUID());
+        Mockito.when(invoice.getInvoiceNumber()).thenReturn(invoiceNumber);
+        Mockito.when(invoice.getInvoiceDate()).thenReturn(new LocalDate(1954, 12, 1));
+        Mockito.when(invoice.getTargetDate()).thenReturn(new LocalDate(2017, 3, 4));
+        Mockito.when(invoice.getCurrency()).thenReturn(Currency.AUD);
+        Mockito.when(invoice.getPaidAmount()).thenReturn(BigDecimal.ZERO);
+        Mockito.when(invoice.getOriginalChargedAmount()).thenReturn(new BigDecimal("1922"));
+        Mockito.when(invoice.getChargedAmount()).thenReturn(new BigDecimal("100293"));
+        Mockito.when(invoice.getCBAAmount()).thenReturn(BigDecimal.TEN);
+        Mockito.when(invoice.getTotalAdjAmount()).thenReturn(new BigDecimal("192"));
+        Mockito.when(invoice.getCreditAdjAmount()).thenReturn(new BigDecimal("283"));
+        Mockito.when(invoice.getRefundAdjAmount()).thenReturn(new BigDecimal("384"));
+        Mockito.when(invoice.getBalance()).thenReturn(new BigDecimal("18376"));
+        Mockito.when(invoice.isMigrationInvoice()).thenReturn(false);
+        Mockito.when(invoice.getCreatedDate()).thenReturn(INVOICE_CREATED_DATE);
+
+        return invoice;
+    }
+
+    protected InvoiceItem createInvoiceItem(final UUID invoiceId, final InvoiceItemType type) {
+        return createInvoiceItem(invoiceId, type, BigDecimal.TEN);
+    }
+
+    protected InvoiceItem createInvoiceItem(final UUID invoiceId, final InvoiceItemType type, final BigDecimal amount) {
+        return createInvoiceItem(invoiceId, type, UUID.randomUUID(), new LocalDate(2013, 1, 2), new LocalDate(2013, 2, 5), amount, null);
+    }
+
+    protected InvoiceItem createInvoiceItem(final UUID invoiceId,
+                                            final InvoiceItemType invoiceItemType,
+                                            final BigDecimal amount,
+                                            final UUID linkedItemId) {
+        return createInvoiceItem(invoiceId, invoiceItemType, UUID.randomUUID(), new LocalDate(2013, 1, 2), new LocalDate(2013, 2, 5), amount, linkedItemId);
+    }
+
+    protected InvoiceItem createInvoiceItem(final UUID invoiceId,
+                                            final InvoiceItemType invoiceItemType,
+                                            final UUID subscriptionId,
+                                            final LocalDate startDate,
+                                            final LocalDate endDate,
+                                            final BigDecimal amount,
+                                            @Nullable final UUID linkedItemId) {
+        final UUID invoiceItemId = UUID.randomUUID();
+
+        final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
+        Mockito.when(invoiceItem.getId()).thenReturn(invoiceItemId);
+        Mockito.when(invoiceItem.getInvoiceItemType()).thenReturn(invoiceItemType);
+        Mockito.when(invoiceItem.getInvoiceId()).thenReturn(invoiceId);
+        Mockito.when(invoiceItem.getAccountId()).thenReturn(UUID.randomUUID());
+        Mockito.when(invoiceItem.getStartDate()).thenReturn(startDate);
+        Mockito.when(invoiceItem.getEndDate()).thenReturn(endDate);
+        Mockito.when(invoiceItem.getAmount()).thenReturn(amount);
+        Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.EUR);
+        Mockito.when(invoiceItem.getDescription()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(invoiceItem.getBundleId()).thenReturn(UUID.randomUUID());
+        Mockito.when(invoiceItem.getSubscriptionId()).thenReturn(subscriptionId);
+        Mockito.when(invoiceItem.getPlanName()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(invoiceItem.getPhaseName()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(invoiceItem.getRate()).thenReturn(new BigDecimal("1203"));
+        Mockito.when(invoiceItem.getLinkedItemId()).thenReturn(linkedItemId);
+        Mockito.when(invoiceItem.getCreatedDate()).thenReturn(INVOICE_CREATED_DATE);
+
+        return invoiceItem;
+    }
+
     @BeforeMethod(groups = "fast")
     public void setUp() throws Exception {
         account = Mockito.mock(Account.class);
@@ -261,7 +336,7 @@ public abstract class AnalyticsTestSuiteNoDB {
         Mockito.when(invoicePayment.getCurrency()).thenReturn(Currency.MXN);
         Mockito.when(invoicePayment.getLinkedInvoicePaymentId()).thenReturn(UUID.randomUUID());
         Mockito.when(invoicePayment.getPaymentCookieId()).thenReturn(UUID.randomUUID());
-        Mockito.when(invoicePayment.getCreatedDate()).thenReturn(new DateTime(2016, 1, 22, 10, 56, 53, DateTimeZone.UTC));
+        Mockito.when(invoicePayment.getCreatedDate()).thenReturn(INVOICE_CREATED_DATE);
         final UUID invoicePaymentId = invoicePayment.getId();
 
         invoice = Mockito.mock(Invoice.class);
@@ -284,7 +359,7 @@ public abstract class AnalyticsTestSuiteNoDB {
         Mockito.when(invoice.getRefundAdjAmount()).thenReturn(new BigDecimal("384"));
         Mockito.when(invoice.getBalance()).thenReturn(new BigDecimal("18376"));
         Mockito.when(invoice.isMigrationInvoice()).thenReturn(false);
-        Mockito.when(invoice.getCreatedDate()).thenReturn(new DateTime(2016, 1, 22, 10, 56, 53, DateTimeZone.UTC));
+        Mockito.when(invoice.getCreatedDate()).thenReturn(INVOICE_CREATED_DATE);
 
         paymentAttempt = Mockito.mock(PaymentAttempt.class);
         Mockito.when(paymentAttempt.getId()).thenReturn(UUID.randomUUID());
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java
index 51fdbb1..39d0020 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessInvoice.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.osgi.bundles.analytics.api;
 
+import java.math.BigDecimal;
+
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -29,9 +31,16 @@ public class TestBusinessInvoice extends AnalyticsTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testConstructor() throws Exception {
+        final BigDecimal balance = BigDecimal.ONE;
+        final BigDecimal amountPaid = BigDecimal.TEN;
+        final BigDecimal amountCharged = BigDecimal.ZERO;
+        final BigDecimal originalAmountCharged = BigDecimal.ONE;
         final BusinessInvoiceModelDao invoiceModelDao = new BusinessInvoiceModelDao(account,
                                                                                     accountRecordId,
                                                                                     invoice,
+                                                                                    amountPaid,
+                                                                                    amountCharged,
+                                                                                    originalAmountCharged,
                                                                                     invoiceRecordId,
                                                                                     auditLog,
                                                                                     tenantRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java
index 1634b9d..349a3d0 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/api/TestBusinessSnapshot.java
@@ -71,6 +71,9 @@ public class TestBusinessSnapshot extends AnalyticsTestSuiteNoDB {
         final BusinessInvoiceModelDao invoiceModelDao = new BusinessInvoiceModelDao(account,
                                                                                     accountRecordId,
                                                                                     invoice,
+                                                                                    BigDecimal.ONE,
+                                                                                    BigDecimal.ONE,
+                                                                                    BigDecimal.ONE,
                                                                                     invoiceRecordId,
                                                                                     auditLog,
                                                                                     tenantRecordId,
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java
index 1d867e8..cd2a065 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceItemModelDao.java
@@ -85,11 +85,12 @@ public class TestBusinessInvoiceItemModelDao extends AnalyticsTestSuiteNoDB {
         Assert.assertEquals(invoiceItemModelDao.getInvoiceDate(), invoice.getInvoiceDate());
         Assert.assertEquals(invoiceItemModelDao.getInvoiceTargetDate(), invoice.getTargetDate());
         Assert.assertEquals(invoiceItemModelDao.getInvoiceCurrency(), invoice.getCurrency().toString());
-        Assert.assertEquals(invoiceItemModelDao.getInvoiceBalance(), invoice.getBalance());
-        Assert.assertEquals(invoiceItemModelDao.getInvoiceAmountPaid(), invoice.getPaidAmount());
-        Assert.assertEquals(invoiceItemModelDao.getInvoiceAmountCharged(), invoice.getChargedAmount());
-        Assert.assertEquals(invoiceItemModelDao.getInvoiceOriginalAmountCharged(), invoice.getOriginalChargedAmount());
-        Assert.assertEquals(invoiceItemModelDao.getInvoiceAmountCredited(), invoice.getCreditAdjAmount());
+        Assert.assertNull(invoiceItemModelDao.getInvoiceBalance());
+        Assert.assertNull(invoiceItemModelDao.getInvoiceAmountPaid());
+        Assert.assertNull(invoiceItemModelDao.getInvoiceAmountCharged());
+        Assert.assertNull(invoiceItemModelDao.getInvoiceOriginalAmountCharged());
+        Assert.assertNull(invoiceItemModelDao.getInvoiceAmountCredited());
+        Assert.assertNull(invoiceItemModelDao.getInvoiceAmountRefunded());
         Assert.assertEquals(invoiceItemModelDao.getItemType(), invoiceItem.getInvoiceItemType().toString());
         //Assert.assertEquals(invoiceItemModelDao.getRevenueRecognizable(), /* TODO */);
         Assert.assertEquals(invoiceItemModelDao.getStartDate(), invoiceItem.getStartDate());
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceModelDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceModelDao.java
index 31bab62..906db0e 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoiceModelDao.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.osgi.bundles.analytics.dao.model;
 
+import java.math.BigDecimal;
+
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -25,13 +27,22 @@ public class TestBusinessInvoiceModelDao extends AnalyticsTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testConstructor() throws Exception {
+        final BigDecimal balance = BigDecimal.ONE;
+        final BigDecimal amountCharged = BigDecimal.ZERO;
+        final BigDecimal originalAmountCharged = BigDecimal.ONE;
+        final BigDecimal amountCredited = BigDecimal.TEN;
+
         final BusinessInvoiceModelDao invoiceModelDao = new BusinessInvoiceModelDao(account,
                                                                                     accountRecordId,
                                                                                     invoice,
+                                                                                    amountCharged,
+                                                                                    originalAmountCharged,
+                                                                                    amountCredited,
                                                                                     invoiceRecordId,
                                                                                     auditLog,
                                                                                     tenantRecordId,
                                                                                     reportGroup);
+
         verifyBusinessModelDaoBase(invoiceModelDao, accountRecordId, tenantRecordId);
         Assert.assertEquals(invoiceModelDao.getCreatedDate(), invoice.getCreatedDate());
         Assert.assertEquals(invoiceModelDao.getInvoiceRecordId(), invoiceRecordId);
@@ -40,10 +51,11 @@ public class TestBusinessInvoiceModelDao extends AnalyticsTestSuiteNoDB {
         Assert.assertEquals(invoiceModelDao.getInvoiceDate(), invoice.getInvoiceDate());
         Assert.assertEquals(invoiceModelDao.getTargetDate(), invoice.getTargetDate());
         Assert.assertEquals(invoiceModelDao.getCurrency(), invoice.getCurrency().toString());
-        Assert.assertEquals(invoiceModelDao.getBalance(), invoice.getBalance());
-        Assert.assertEquals(invoiceModelDao.getAmountPaid(), invoice.getPaidAmount());
-        Assert.assertEquals(invoiceModelDao.getAmountCharged(), invoice.getChargedAmount());
-        Assert.assertEquals(invoiceModelDao.getOriginalAmountCharged(), invoice.getOriginalChargedAmount());
-        Assert.assertEquals(invoiceModelDao.getAmountCredited(), invoice.getCreditAdjAmount());
+        Assert.assertEquals(invoiceModelDao.getAmountCharged(), amountCharged);
+        Assert.assertEquals(invoiceModelDao.getOriginalAmountCharged(), originalAmountCharged);
+        Assert.assertEquals(invoiceModelDao.getAmountCredited(), amountCredited);
+        Assert.assertNull(invoiceModelDao.getBalance());
+        Assert.assertNull(invoiceModelDao.getAmountPaid());
+        Assert.assertNull(invoiceModelDao.getAmountRefunded());
     }
 }
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoicePaymentModelDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoicePaymentModelDao.java
index 51118ca..bcdac82 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoicePaymentModelDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/model/TestBusinessInvoicePaymentModelDao.java
@@ -104,11 +104,12 @@ public class TestBusinessInvoicePaymentModelDao extends AnalyticsTestSuiteNoDB {
         Assert.assertEquals(invoicePaymentModelDao.getInvoiceDate(), invoice.getInvoiceDate());
         Assert.assertEquals(invoicePaymentModelDao.getInvoiceTargetDate(), invoice.getTargetDate());
         Assert.assertEquals(invoicePaymentModelDao.getInvoiceCurrency(), invoice.getCurrency().toString());
-        Assert.assertEquals(invoicePaymentModelDao.getInvoiceBalance(), invoice.getBalance());
-        Assert.assertEquals(invoicePaymentModelDao.getInvoiceAmountPaid(), invoice.getPaidAmount());
-        Assert.assertEquals(invoicePaymentModelDao.getInvoiceAmountCharged(), invoice.getChargedAmount());
-        Assert.assertEquals(invoicePaymentModelDao.getInvoiceOriginalAmountCharged(), invoice.getOriginalChargedAmount());
-        Assert.assertEquals(invoicePaymentModelDao.getInvoiceAmountCredited(), invoice.getCreditAdjAmount());
+        Assert.assertNull(invoicePaymentModelDao.getInvoiceBalance());
+        Assert.assertNull(invoicePaymentModelDao.getInvoiceAmountPaid());
+        Assert.assertNull(invoicePaymentModelDao.getInvoiceAmountCharged());
+        Assert.assertNull(invoicePaymentModelDao.getInvoiceOriginalAmountCharged());
+        Assert.assertNull(invoicePaymentModelDao.getInvoiceAmountCredited());
+        Assert.assertNull(invoicePaymentModelDao.getInvoiceAmountRefunded());
         Assert.assertEquals(invoicePaymentModelDao.getInvoicePaymentType(), invoicePayment.getType().toString());
         Assert.assertEquals(invoicePaymentModelDao.getPaymentNumber(), (Long) payment.getPaymentNumber().longValue());
         Assert.assertEquals(invoicePaymentModelDao.getLinkedInvoicePaymentId(), invoicePayment.getLinkedInvoicePaymentId());
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java
index abd47c7..32d2f67 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessAnalyticsSqlDao.java
@@ -153,6 +153,9 @@ public class TestBusinessAnalyticsSqlDao extends AnalyticsTestSuiteWithEmbeddedD
         final BusinessInvoiceModelDao businessInvoiceModelDao = new BusinessInvoiceModelDao(account,
                                                                                             accountRecordId,
                                                                                             invoice,
+                                                                                            BigDecimal.ONE,
+                                                                                            BigDecimal.ONE,
+                                                                                            BigDecimal.ONE,
                                                                                             invoiceRecordId,
                                                                                             auditLog,
                                                                                             tenantRecordId,
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
new file mode 100644
index 0000000..60f46e9
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
@@ -0,0 +1,195 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.sql.DataSource;
+
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
+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.payment.api.Payment;
+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;
+
+public class TestBusinessInvoiceAndInvoicePaymentDao extends AnalyticsTestSuiteNoDB {
+
+    private BusinessInvoiceAndInvoicePaymentDao dao;
+    private OSGIKillbillAPI osgiKillbillApi;
+
+    @Override
+    @BeforeMethod(groups = "fast")
+    public void setUp() throws Exception {
+        super.setUp();
+
+        final OSGIKillbillDataSource osgiKillbillDataSource = Mockito.mock(OSGIKillbillDataSource.class);
+
+        final DataSource dataSource = Mockito.mock(DataSource.class);
+        Mockito.when(osgiKillbillDataSource.getDataSource()).thenReturn(dataSource);
+
+        final OSGIKillbillLogService osgiKillbillLogService = Mockito.mock(OSGIKillbillLogService.class);
+        Mockito.doAnswer(new Answer() {
+            @Override
+            public Object answer(final InvocationOnMock invocation) throws Throwable {
+                logger.info(Arrays.toString(invocation.getArguments()));
+                return null;
+            }
+        }).when(osgiKillbillLogService).log(Mockito.anyInt(), Mockito.anyString());
+
+        osgiKillbillApi = Mockito.mock(OSGIKillbillAPI.class, Mockito.RETURNS_DEEP_STUBS);
+
+        final BusinessAccountDao businessAccountDao = new BusinessAccountDao(osgiKillbillLogService, osgiKillbillApi, osgiKillbillDataSource);
+        dao = new BusinessInvoiceAndInvoicePaymentDao(osgiKillbillLogService, osgiKillbillApi, osgiKillbillDataSource, businessAccountDao);
+    }
+
+    @Test(groups = "fast")
+    public void testVerifyDenormalizationFillUp() throws Exception {
+        /*
+         * Invoice 349:
+	     *  +588 (recurring1)
+	     *  -588 (repair)
+	     *  +588 (cba)
+         */
+        final UUID invoice349Id = UUID.randomUUID();
+        final InvoiceItem invoiceItem349Recurring1 = createInvoiceItem(invoice349Id, InvoiceItemType.RECURRING, new BigDecimal("588"));
+        final InvoiceItem invoiceItem349Repair = createInvoiceItem(invoice349Id, InvoiceItemType.REPAIR_ADJ, new BigDecimal("-588"), invoiceItem349Recurring1.getId());
+        final InvoiceItem invoiceItem349Cba = createInvoiceItem(invoice349Id, InvoiceItemType.CBA_ADJ, new BigDecimal("588"));
+        final Invoice invoice349 = createInvoice(invoice349Id, 349, ImmutableList.<InvoiceItem>of(invoiceItem349Recurring1, invoiceItem349Repair, invoiceItem349Cba));
+
+        final BigDecimal balance349 = BigDecimal.ZERO;
+        final BigDecimal amountPaid349 = new BigDecimal("588");
+        final BigDecimal amountCharged349 = new BigDecimal("27.40");
+        final BigDecimal originalAmountCharged349 = new BigDecimal("588");
+        final BigDecimal amountCredited349 = new BigDecimal("560.60");
+        final BigDecimal amountRefunded349 = BigDecimal.ZERO;
+
+        /*
+         * Invoice 570:
+	     *  +27.40 (recurring1 proration)
+	     *  +42.29 (recurring2)
+	     *  -69.69 (cba use)
+         */
+        final UUID invoice570Id = UUID.randomUUID();
+        final InvoiceItem invoiceItem570Recurring1Proration = createInvoiceItem(invoice570Id,
+                                                                                InvoiceItemType.RECURRING,
+                                                                                invoiceItem349Recurring1.getSubscriptionId(),
+                                                                                invoiceItem349Recurring1.getStartDate(),
+                                                                                invoiceItem349Recurring1.getEndDate().minusDays(1),
+                                                                                new BigDecimal("27.40"),
+                                                                                null);
+        final InvoiceItem invoiceItem570Recurring2 = createInvoiceItem(invoice570Id, InvoiceItemType.RECURRING, new BigDecimal("42.29"));
+        final InvoiceItem invoiceItem570Cba = createInvoiceItem(invoice570Id, InvoiceItemType.CBA_ADJ, new BigDecimal("-69.69"));
+        final Invoice invoice570 = createInvoice(invoice570Id, 570, ImmutableList.<InvoiceItem>of(invoiceItem570Recurring1Proration, invoiceItem570Recurring2, invoiceItem570Cba));
+
+        final BigDecimal balance570 = BigDecimal.ZERO;
+        final BigDecimal amountPaid570 = BigDecimal.ZERO;
+        final BigDecimal amountCharged570 = new BigDecimal("42.29");
+        final BigDecimal originalAmountCharged570 = new BigDecimal("42.29");
+        final BigDecimal amountCredited570 = new BigDecimal("-42.29");
+        final BigDecimal amountRefunded570 = BigDecimal.ZERO;
+
+        // Setup the mocks
+        // TODO this is really fragile - we need to extract a mock library for testing Kill Bill
+        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);
+
+        Mockito.when(payment.getAmount()).thenReturn(amountPaid349);
+        Mockito.when(osgiKillbillApi.getPaymentApi().getAccountPayments(account.getId(), callContext)).thenReturn(ImmutableList.<Payment>of(payment));
+
+        Mockito.when(invoicePayment.getInvoiceId()).thenReturn(invoice349Id);
+        Mockito.when(invoicePayment.getAmount()).thenReturn(amountPaid349);
+        Mockito.when(osgiKillbillApi.getInvoicePaymentApi().getInvoicePayments(payment.getId(), callContext)).thenReturn(ImmutableList.<InvoicePayment>of(invoicePayment));
+
+        // Compute the pojos
+        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);
+
+        /*
+         * Expected Business invoice 349:
+         *  BII : 	+588	(recurring1)
+         *  BIIA :	-560.60
+         *  BIIC:	+560.60
+         *
+         * Expected Business invoice 570:
+         *  BII : 	+42.29	(recurring2)
+         *  BIIC:	-42.29
+         */
+        Assert.assertEquals(invoices.keySet().size(), 2);
+
+        Assert.assertEquals(invoices.get(invoice349Id).getBalance().compareTo(balance349), 0);
+        Assert.assertEquals(invoices.get(invoice349Id).getAmountPaid().compareTo(amountPaid349), 0);
+        Assert.assertEquals(invoices.get(invoice349Id).getAmountCharged().compareTo(amountCharged349), 0);
+        Assert.assertEquals(invoices.get(invoice349Id).getOriginalAmountCharged().compareTo(originalAmountCharged349), 0);
+        Assert.assertEquals(invoices.get(invoice349Id).getAmountCredited().compareTo(amountCredited349), 0);
+        Assert.assertEquals(invoices.get(invoice349Id).getAmountRefunded().compareTo(amountRefunded349), 0);
+
+        Assert.assertEquals(invoices.get(invoice570Id).getBalance().compareTo(balance570), 0);
+        Assert.assertEquals(invoices.get(invoice570Id).getAmountPaid().compareTo(amountPaid570), 0);
+        Assert.assertEquals(invoices.get(invoice570Id).getAmountCharged().compareTo(amountCharged570), 0);
+        Assert.assertEquals(invoices.get(invoice570Id).getOriginalAmountCharged().compareTo(originalAmountCharged570), 0);
+        Assert.assertEquals(invoices.get(invoice570Id).getAmountCredited().compareTo(amountCredited570), 0);
+        Assert.assertEquals(invoices.get(invoice570Id).getAmountRefunded().compareTo(amountRefunded570), 0);
+
+        Assert.assertEquals(invoiceItems.get(invoice349Id).size(), 3);
+        for (final BusinessInvoiceItemBaseModelDao invoiceItem : invoiceItems.get(invoice349Id)) {
+            if (InvoiceItemType.RECURRING.toString().equals(invoiceItem.getItemType())) {
+                Assert.assertEquals(invoiceItem.getAmount().compareTo(new BigDecimal("588")), 0, String.format("RECURRING item is %s, not 588", invoiceItem.getAmount()));
+            } else if (InvoiceItemType.ITEM_ADJ.toString().equals(invoiceItem.getItemType())) {
+                Assert.assertEquals(invoiceItem.getAmount().compareTo(new BigDecimal("-560.60")), 0, String.format("ITEM_ADJ item is %s, not -560.60", invoiceItem.getAmount()));
+            } else if (InvoiceItemType.CBA_ADJ.toString().equals(invoiceItem.getItemType())) {
+                Assert.assertEquals(invoiceItem.getAmount().compareTo(new BigDecimal("560.60")), 0, String.format("CBA item is %s, not 560.60", invoiceItem.getAmount()));
+            } else {
+                Assert.fail();
+            }
+        }
+
+        Assert.assertEquals(invoiceItems.get(invoice570Id).size(), 2);
+        for (final BusinessInvoiceItemBaseModelDao invoiceItem : invoiceItems.get(invoice570Id)) {
+            if (InvoiceItemType.RECURRING.toString().equals(invoiceItem.getItemType())) {
+                Assert.assertEquals(invoiceItem.getAmount().compareTo(new BigDecimal("42.29")), 0, String.format("RECURRING item is %s, not 42.29", invoiceItem.getAmount()));
+            } else if (InvoiceItemType.CBA_ADJ.toString().equals(invoiceItem.getItemType())) {
+                Assert.assertEquals(invoiceItem.getAmount().compareTo(new BigDecimal("-42.29")), 0, String.format("CBA item is %s, not -42.29", invoiceItem.getAmount()));
+            } else {
+                Assert.fail();
+            }
+        }
+    }
+}
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java
index c063011..1f1e381 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceDao.java
@@ -21,11 +21,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
 import javax.sql.DataSource;
 
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
@@ -34,11 +31,11 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.osgi.bundles.analytics.AnalyticsTestSuiteNoDB;
 import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao;
+import com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
 import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
@@ -46,9 +43,6 @@ import com.google.common.collect.ImmutableList;
 
 public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
 
-    private final UUID accountId = UUID.randomUUID();
-    private final UUID bundleId = UUID.randomUUID();
-
     private BusinessInvoiceDao invoiceDao;
 
     @Override
@@ -70,7 +64,7 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
             }
         }).when(osgiKillbillLogService).log(Mockito.anyInt(), Mockito.anyString());
 
-        invoiceDao = new BusinessInvoiceDao(osgiKillbillLogService, null, osgiKillbillDataSource, null);
+        invoiceDao = new BusinessInvoiceDao(osgiKillbillLogService, null, osgiKillbillDataSource);
     }
 
     @Test(groups = "fast")
@@ -196,22 +190,22 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
     public void testInvoiceAdjustment() throws Exception {
         final UUID invoiceId = UUID.randomUUID();
 
-        Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING),
-                                                              ImmutableList.<InvoiceItem>of()));
-        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(createInvoiceItem(invoiceId, InvoiceItemType.REFUND_ADJ),
-                                                             ImmutableList.<InvoiceItem>of()));
+        Assert.assertFalse(BusinessInvoiceUtils.isInvoiceAdjustmentItem(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING),
+                                                                        ImmutableList.<InvoiceItem>of()));
+        Assert.assertTrue(BusinessInvoiceUtils.isInvoiceAdjustmentItem(createInvoiceItem(invoiceId, InvoiceItemType.REFUND_ADJ),
+                                                                       ImmutableList.<InvoiceItem>of()));
 
         final InvoiceItem creditAdj = createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ);
 
         // Account credit
-        Assert.assertFalse(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
-                                                              ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
-
-        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
-                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate().add(BigDecimal.ONE)))));
-        Assert.assertTrue(invoiceDao.isInvoiceAdjustmentItem(creditAdj,
-                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING),
-                                                                                           createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
+        Assert.assertFalse(BusinessInvoiceUtils.isInvoiceAdjustmentItem(creditAdj,
+                                                                        ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
+
+        Assert.assertTrue(BusinessInvoiceUtils.isInvoiceAdjustmentItem(creditAdj,
+                                                                       ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate().add(BigDecimal.ONE)))));
+        Assert.assertTrue(BusinessInvoiceUtils.isInvoiceAdjustmentItem(creditAdj,
+                                                                       ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING),
+                                                                                                     createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, creditAdj.getAmount().negate()))));
     }
 
     @Test(groups = "fast")
@@ -276,42 +270,4 @@ public class TestBusinessInvoiceDao extends AnalyticsTestSuiteNoDB {
             }
         }
     }
-
-    private InvoiceItem createInvoiceItem(final UUID invoiceId, final InvoiceItemType type) {
-        return createInvoiceItem(invoiceId, type, BigDecimal.TEN);
-    }
-
-    private InvoiceItem createInvoiceItem(final UUID invoiceId, final InvoiceItemType type, final BigDecimal amount) {
-        return createInvoiceItem(invoiceId, type, UUID.randomUUID(), new LocalDate(2013, 1, 2), new LocalDate(2013, 2, 5), amount, null);
-    }
-
-    private InvoiceItem createInvoiceItem(final UUID invoiceId,
-                                          final InvoiceItemType invoiceItemType,
-                                          final UUID subscriptionId,
-                                          final LocalDate startDate,
-                                          final LocalDate endDate,
-                                          final BigDecimal amount,
-                                          @Nullable final UUID linkedItemId) {
-        final UUID invoiceItemId = UUID.randomUUID();
-
-        final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
-        Mockito.when(invoiceItem.getId()).thenReturn(invoiceItemId);
-        Mockito.when(invoiceItem.getInvoiceItemType()).thenReturn(invoiceItemType);
-        Mockito.when(invoiceItem.getInvoiceId()).thenReturn(invoiceId);
-        Mockito.when(invoiceItem.getAccountId()).thenReturn(accountId);
-        Mockito.when(invoiceItem.getStartDate()).thenReturn(startDate);
-        Mockito.when(invoiceItem.getEndDate()).thenReturn(endDate);
-        Mockito.when(invoiceItem.getAmount()).thenReturn(amount);
-        Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.EUR);
-        Mockito.when(invoiceItem.getDescription()).thenReturn(UUID.randomUUID().toString());
-        Mockito.when(invoiceItem.getBundleId()).thenReturn(bundleId);
-        Mockito.when(invoiceItem.getSubscriptionId()).thenReturn(subscriptionId);
-        Mockito.when(invoiceItem.getPlanName()).thenReturn(UUID.randomUUID().toString());
-        Mockito.when(invoiceItem.getPhaseName()).thenReturn(UUID.randomUUID().toString());
-        Mockito.when(invoiceItem.getRate()).thenReturn(new BigDecimal("1203"));
-        Mockito.when(invoiceItem.getLinkedItemId()).thenReturn(linkedItemId);
-        Mockito.when(invoiceItem.getCreatedDate()).thenReturn(new DateTime(2016, 1, 22, 10, 56, 51, DateTimeZone.UTC));
-
-        return invoiceItem;
-    }
 }