killbill-memoizeit

Invoice refactoring for CBA

4/15/2013 10:35:48 PM

Details

diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/CBADao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/CBADao.java
new file mode 100644
index 0000000..e3d88c6
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/CBADao.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
+import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.EntityPersistenceException;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+
+import com.google.common.collect.Ordering;
+
+public class CBADao {
+
+    private final InvoiceDaoHelper invoiceDaoHelper;
+
+    public CBADao() {
+        this.invoiceDaoHelper = new InvoiceDaoHelper();
+    }
+
+
+    public BigDecimal getAccountCBAFromTransaction(final UUID accountId,
+                                                    final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
+                                                    final InternalTenantContext context) {
+        final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+        return getAccountCBAFromTransaction(invoices);
+    }
+
+    public BigDecimal getAccountCBAFromTransaction(final List<InvoiceModelDao> invoices) {
+        BigDecimal cba = BigDecimal.ZERO;
+        for (final InvoiceModelDao cur : invoices) {
+            final InvoiceItemList invoiceItems = new InvoiceItemList(cur.getInvoiceItems());
+            cba = cba.add(invoiceItems.getCBAAmount());
+        }
+        return cba;
+    }
+
+    public void doCBAComplexity(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+
+        final List<InvoiceModelDao> invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+        for (InvoiceModelDao cur : invoiceItemModelDaos) {
+            addCBAIfNeeded(entitySqlDaoWrapperFactory, cur, context);
+        }
+        useExistingCBAFromTransaction(invoiceItemModelDaos, entitySqlDaoWrapperFactory, context);
+    }
+
+    /**
+     * Adjust the invoice with a CBA item if the new invoice balance is negative.
+     *
+     * @param entitySqlDaoWrapperFactory the EntitySqlDaoWrapperFactory from the current transaction
+     * @param invoice                    the invoice to adjust
+     * @param context                    the call context
+     */
+    private void addCBAIfNeeded(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
+                                final InvoiceModelDao invoice,
+                                final InternalCallContext context) throws EntityPersistenceException {
+
+        // If invoice balance becomes negative we add some CBA item
+        final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
+        if (balance.compareTo(BigDecimal.ZERO) < 0) {
+            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+            final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), balance.negate(), invoice.getCurrency()));
+            transInvoiceItemDao.create(cbaAdjItem, context);
+        }
+    }
+
+
+    private void useExistingCBAFromTransaction(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws InvoiceApiException, EntityPersistenceException {
+
+        final BigDecimal accountCBA = getAccountCBAFromTransaction(invoices);
+        if (accountCBA.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        final List<InvoiceModelDao> unpaidInvoices = invoiceDaoHelper.getUnpaidInvoicesByAccountFromTransaction(invoices, null);
+        // We order the same os BillingStateCalculator-- should really share the comparator
+        final List<InvoiceModelDao> orderedUnpaidInvoices = Ordering.from(new Comparator<InvoiceModelDao>() {
+            @Override
+            public int compare(final InvoiceModelDao i1, final InvoiceModelDao i2) {
+                return i1.getInvoiceDate().compareTo(i2.getInvoiceDate());
+            }
+        }).immutableSortedCopy(unpaidInvoices);
+
+        BigDecimal remainingAccountCBA = accountCBA;
+        for (InvoiceModelDao cur : orderedUnpaidInvoices) {
+            final BigDecimal curInvoiceBalance = InvoiceModelDaoHelper.getBalance(cur);
+            final BigDecimal cbaToApplyOnInvoice = remainingAccountCBA.compareTo(curInvoiceBalance) <= 0 ? remainingAccountCBA : curInvoiceBalance;
+            remainingAccountCBA = remainingAccountCBA.subtract(cbaToApplyOnInvoice);
+
+            final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(cur.getId(), cur.getAccountId(), context.getCreatedDate().toLocalDate(), cbaToApplyOnInvoice.negate(), cur.getCurrency()));
+
+            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+            transInvoiceItemDao.create(cbaAdjItem, context);
+
+            if (remainingAccountCBA.compareTo(BigDecimal.ZERO) <= 0) {
+                break;
+            }
+        }
+    }
+
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index ee5886c..dd614eb 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -39,6 +39,8 @@ import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.api.InvoicePayment.InvoicePaymentType;
 import com.ning.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
+import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import com.ning.billing.invoice.model.InvoiceItemList;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
@@ -69,6 +71,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
     private final NextBillingDatePoster nextBillingDatePoster;
     private final InternalBus eventBus;
+    private final InvoiceDaoHelper invoiceDaoHelper;
+    private final CBADao cbaDao;
 
     @Inject
     public DefaultInvoiceDao(final IDBI dbi,
@@ -80,6 +84,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao), InvoiceSqlDao.class);
         this.nextBillingDatePoster = nextBillingDatePoster;
         this.eventBus = eventBus;
+        this.invoiceDaoHelper = new InvoiceDaoHelper();
+        this.cbaDao =  new CBADao();
     }
 
     @Override
@@ -95,7 +101,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
 
                 final List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(accountId.toString(), context);
-                populateChildren(invoices, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
 
                 return invoices;
             }
@@ -107,7 +113,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
             @Override
             public List<InvoiceModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                return invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
             }
         });
     }
@@ -122,7 +128,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccountAfterDate(accountId.toString(),
                                                                                                 fromDate.toDateTimeAtStartOfDay().toDate(),
                                                                                                 context);
-                populateChildren(invoices, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
 
                 return invoices;
             }
@@ -137,7 +143,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
 
                 final List<InvoiceModelDao> invoices = invoiceDao.get(context);
-                populateChildren(invoices, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
 
                 return invoices;
             }
@@ -155,7 +161,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 if (invoice == null) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
                 }
-                populateChildren(invoice, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
                 return invoice;
             }
         });
@@ -177,7 +183,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 if (invoice == null) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_NUMBER_NOT_FOUND, number.longValue());
                 }
-                populateChildren(invoice, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
                 return invoice;
             }
         });
@@ -206,8 +212,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                         transInvoiceItemSqlDao.create(invoiceItemModelDao, context);
                     }
 
-                    // Now we check whether we generated any credit that could be used on some unpaid invoices
-                    useExistingCBAFromTransaction(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
+                    cbaDao.doCBAComplexity(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
 
                     notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoice.getAccountId(), callbackDateTimePerSubscriptions, context.getUserToken());
 
@@ -229,7 +234,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
 
                 final List<InvoiceModelDao> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId.toString(), context);
-                populateChildren(invoices, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoices, entitySqlDaoWrapperFactory, context);
 
                 return invoices;
             }
@@ -244,7 +249,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 BigDecimal cba = BigDecimal.ZERO;
 
                 BigDecimal accountBalance = BigDecimal.ZERO;
-                final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
                 for (final InvoiceModelDao cur : invoices) {
                     accountBalance = accountBalance.add(InvoiceModelDaoHelper.getBalance(cur));
                     cba = cba.add(InvoiceModelDaoHelper.getCBAAmount(cur));
@@ -259,7 +264,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<BigDecimal>() {
             @Override
             public BigDecimal inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                return cbaDao.getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
             }
         });
     }
@@ -269,7 +274,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
             @Override
             public List<InvoiceModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return getUnpaidInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, upToDate, context);
+                return invoiceDaoHelper.getUnpaidInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, upToDate, context);
             }
         });
     }
@@ -315,13 +320,13 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 }
 
                 // Retrieve the amounts to adjust, if needed
-                final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = computeItemAdjustments(payment.getInvoiceId().toString(),
+                final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = invoiceDaoHelper.computeItemAdjustments(payment.getInvoiceId().toString(),
                                                                                                entitySqlDaoWrapperFactory,
                                                                                                invoiceItemIdsWithNullAmounts,
                                                                                                context);
 
                 // Compute the actual amount to refund
-                final BigDecimal requestedPositiveAmount = computePositiveRefundAmount(payment, requestedRefundAmount, invoiceItemIdsWithAmounts);
+                final BigDecimal requestedPositiveAmount = invoiceDaoHelper.computePositiveRefundAmount(payment, requestedRefundAmount, invoiceItemIdsWithAmounts);
 
                 // Before we go further, check if that refund already got inserted -- the payment system keeps a state machine
                 // and so this call may be called several time for the same  paymentCookieId (which is really the refundId)
@@ -339,7 +344,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 // Retrieve invoice after the Refund
                 final InvoiceModelDao invoice = transInvoiceDao.getById(payment.getInvoiceId().toString(), context);
                 if (invoice != null) {
-                    populateChildren(invoice, entitySqlDaoWrapperFactory, context);
+                    invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
                 } else {
                     throw new IllegalStateException("Invoice shouldn't be null for payment " + payment.getId());
                 }
@@ -364,15 +369,14 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     // Invoice item adjustment
                     for (final UUID invoiceItemId : invoiceItemIdsWithAmounts.keySet()) {
                         final BigDecimal adjAmount = invoiceItemIdsWithAmounts.get(invoiceItemId);
-                        final InvoiceItemModelDao item = createAdjustmentItem(entitySqlDaoWrapperFactory, invoice.getId(), invoiceItemId, adjAmount,
+                        final InvoiceItemModelDao item = invoiceDaoHelper.createAdjustmentItem(entitySqlDaoWrapperFactory, invoice.getId(), invoiceItemId, adjAmount,
                                                                               invoice.getCurrency(), context.getCreatedDate().toLocalDate(),
                                                                               context);
                         transInvoiceItemDao.create(item, context);
                     }
                 }
 
-                // Now we check whether we have any credit that could be used on some unpaid invoices (for which payment was just refunded)
-                useExistingCBAFromTransaction(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
+                cbaDao.doCBAComplexity(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoice.getId(), invoice.getAccountId(), context.getUserToken(), context);
@@ -382,105 +386,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
-    /**
-     * Find amounts to adjust for individual items, if not specified.
-     * The user gives us a list of items to adjust associated with a given amount (how much to refund per invoice item).
-     * In case of full adjustments, the amount can be null: in this case, we retrieve the original amount for the invoice
-     * item.
-     *
-     * @param invoiceId                     original invoice id
-     * @param entitySqlDaoWrapperFactory    the EntitySqlDaoWrapperFactory from the current transaction
-     * @param invoiceItemIdsWithNullAmounts the original mapping between invoice item ids and amount to refund (contains null)
-     * @param context                       the tenant context
-     * @return the final mapping between invoice item ids and amount to refund
-     * @throws InvoiceApiException
-     */
-    private Map<UUID, BigDecimal> computeItemAdjustments(final String invoiceId,
-                                                         final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
-                                                         final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts,
-                                                         final InternalTenantContext context) throws InvoiceApiException {
-        // Populate the missing amounts for individual items, if needed
-        final Builder<UUID, BigDecimal> invoiceItemIdsWithAmountsBuilder = new Builder<UUID, BigDecimal>();
-        if (invoiceItemIdsWithNullAmounts.size() == 0) {
-            return invoiceItemIdsWithAmountsBuilder.build();
-        }
-
-        // Retrieve invoice before the Refund
-        final InvoiceModelDao invoice = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getById(invoiceId, context);
-        if (invoice != null) {
-            populateChildren(invoice, entitySqlDaoWrapperFactory, context);
-        } else {
-            throw new IllegalStateException("Invoice shouldn't be null for id " + invoiceId);
-        }
-
-        for (final UUID invoiceItemId : invoiceItemIdsWithNullAmounts.keySet()) {
-            final BigDecimal adjAmount = Objects.firstNonNull(invoiceItemIdsWithNullAmounts.get(invoiceItemId),
-                                                              getInvoiceItemAmountForId(invoice, invoiceItemId));
-            final BigDecimal adjAmountRemainingAfterRepair = computeItemAdjustmentAmount(invoiceItemId, adjAmount, invoice.getInvoiceItems());
-            if (adjAmountRemainingAfterRepair.compareTo(BigDecimal.ZERO) > 0) {
-                invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, adjAmountRemainingAfterRepair);
-            }
-        }
-
-        return invoiceItemIdsWithAmountsBuilder.build();
-    }
-
-    /**
-     * @param invoiceItem  item we are adjusting
-     * @param requestedPositiveAmountToAdjust
-     *                     amount we are adjusting for that item
-     * @param invoiceItems list of all invoice items on this invoice
-     * @return the amount we should really adjust based on whether or not the item got repaired
-     */
-    private BigDecimal computeItemAdjustmentAmount(final UUID invoiceItem, final BigDecimal requestedPositiveAmountToAdjust, final List<InvoiceItemModelDao> invoiceItems) {
-
-        BigDecimal positiveRepairedAmount = BigDecimal.ZERO;
-
-        final Collection<InvoiceItemModelDao> repairedItems = Collections2.filter(invoiceItems, new Predicate<InvoiceItemModelDao>() {
-            @Override
-            public boolean apply(final InvoiceItemModelDao input) {
-                return (input.getType() == InvoiceItemType.REPAIR_ADJ && input.getLinkedItemId().equals(invoiceItem));
-            }
-        });
-        for (final InvoiceItemModelDao cur : repairedItems) {
-            // Repair item are negative so we negate to make it positive
-            positiveRepairedAmount = positiveRepairedAmount.add(cur.getAmount().negate());
-        }
-        return (positiveRepairedAmount.compareTo(requestedPositiveAmountToAdjust) >= 0) ? BigDecimal.ZERO : requestedPositiveAmountToAdjust.subtract(positiveRepairedAmount);
-    }
 
-    private BigDecimal getInvoiceItemAmountForId(final InvoiceModelDao invoice, final UUID invoiceItemId) throws InvoiceApiException {
-        for (final InvoiceItemModelDao invoiceItem : invoice.getInvoiceItems()) {
-            if (invoiceItem.getId().equals(invoiceItemId)) {
-                return invoiceItem.getAmount();
-            }
-        }
-
-        throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
-    }
-
-    @VisibleForTesting
-    static BigDecimal computePositiveRefundAmount(final InvoicePaymentModelDao payment, final BigDecimal requestedAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
-        final BigDecimal maxRefundAmount = payment.getAmount() == null ? BigDecimal.ZERO : payment.getAmount();
-        final BigDecimal requestedPositiveAmount = requestedAmount == null ? maxRefundAmount : requestedAmount;
-        // This check is good but not enough, we need to also take into account previous refunds
-        // (But that should have been checked in the payment call already)
-        if (requestedPositiveAmount.compareTo(maxRefundAmount) > 0) {
-            throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_TOO_HIGH, requestedPositiveAmount, maxRefundAmount);
-        }
-
-        // Verify if the requested amount matches the invoice items to adjust, if specified
-        BigDecimal amountFromItems = BigDecimal.ZERO;
-        for (final BigDecimal itemAmount : invoiceItemIdsWithAmounts.values()) {
-            amountFromItems = amountFromItems.add(itemAmount);
-        }
-
-        // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
-        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && requestedPositiveAmount.compareTo(amountFromItems) != 0) {
-            throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST, requestedPositiveAmount, amountFromItems);
-        }
-        return requestedPositiveAmount;
-    }
 
     @Override
     public InvoicePaymentModelDao postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final InternalCallContext context) throws InvoiceApiException {
@@ -490,7 +396,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
             public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
                 final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
 
-                final BigDecimal maxChargedBackAmount = getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
+                final BigDecimal maxChargedBackAmount = invoiceDaoHelper.getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
                 final BigDecimal requestedChargedBackAmout = (amount == null) ? maxChargedBackAmount : amount;
                 if (requestedChargedBackAmout.compareTo(BigDecimal.ZERO) <= 0) {
                     throw new InvoiceApiException(ErrorCode.CHARGE_BACK_AMOUNT_IS_NEGATIVE);
@@ -510,10 +416,10 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 // Notify the bus since the balance of the invoice changed
                 final UUID accountId = transactional.getAccountIdFromInvoicePaymentId(chargeBack.getId().toString(), context);
-                notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, payment.getInvoiceId(), accountId, context.getUserToken(), context);
 
-                // Now we check whether we have any credit that could be used on some unpaid invoices (for which payment was just charged back)
-                useExistingCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
+
+                notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, payment.getInvoiceId(), accountId, context.getUserToken(), context);
 
                 return chargeBack;
             }
@@ -525,7 +431,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<BigDecimal>() {
             @Override
             public BigDecimal inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
+                return invoiceDaoHelper.getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
             }
         });
     }
@@ -626,15 +532,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
                 transInvoiceItemDao.create(externalCharge, context);
 
-                // At this point, reread the invoice and figure out if we need to consume some of the CBA
-                final InvoiceModelDao invoice = transactional.getById(invoiceIdForExternalCharge.toString(), context);
-                if (invoice == null) {
-                    throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceIdForExternalCharge);
-                }
-                populateChildren(invoice, entitySqlDaoWrapperFactory, context);
 
-                // Now we check whether we have any credit that could be used towards that charge
-                useExistingCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
                 // TODO should we post an InvoiceCreationInternalEvent event instead? Note! This will trigger a payment (see InvoiceHandler)
@@ -676,7 +575,9 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                                                            accountId, null, null, null, null, effectiveDate,
                                                                            null, positiveCreditAmount.negate(), null,
                                                                            currency, null);
-                insertItemAndAddCBAIfNeeded(entitySqlDaoWrapperFactory, credit, context);
+                invoiceDaoHelper.insertItem(entitySqlDaoWrapperFactory, credit, context);
+
+                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
@@ -693,9 +594,12 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<InvoiceItemModelDao>() {
             @Override
             public InvoiceItemModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                final InvoiceItemModelDao invoiceItemAdjustment = createAdjustmentItem(entitySqlDaoWrapperFactory, invoiceId, invoiceItemId, positiveAdjAmount,
+                final InvoiceItemModelDao invoiceItemAdjustment = invoiceDaoHelper.createAdjustmentItem(entitySqlDaoWrapperFactory, invoiceId, invoiceItemId, positiveAdjAmount,
                                                                                        currency, effectiveDate, context);
-                insertItemAndAddCBAIfNeeded(entitySqlDaoWrapperFactory, invoiceItemAdjustment, context);
+                invoiceDaoHelper.insertItem(entitySqlDaoWrapperFactory, invoiceItemAdjustment, context);
+
+                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
+
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
 
                 return invoiceItemAdjustment;
@@ -730,14 +634,14 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 invoiceItemSqlDao.create(cbaAdjItem, context);
 
                 // Verify the final invoice balance is not negative
-                populateChildren(invoice, entitySqlDaoWrapperFactory, context);
+                invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
                 if (InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO) < 0) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_WOULD_BE_NEGATIVE);
                 }
 
                 // If there is more account credit than CBA we adjusted, we're done.
                 // Otherwise, we need to find further invoices on which this credit was consumed
-                final BigDecimal accountCBA = getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                final BigDecimal accountCBA = cbaDao.getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
                 if (accountCBA.compareTo(BigDecimal.ZERO) < 0) {
                     if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
                         throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
@@ -745,7 +649,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     final List<InvoiceModelDao> invoicesFollowing = transactional.getInvoicesByAccountAfterDate(accountId.toString(),
                                                                                                                 invoice.getInvoiceDate().toDateTimeAtStartOfDay().toDate(),
                                                                                                                 context);
-                    populateChildren(invoicesFollowing, entitySqlDaoWrapperFactory, context);
+                    invoiceDaoHelper.populateChildren(invoicesFollowing, entitySqlDaoWrapperFactory, context);
 
                     // The remaining amount to adjust (i.e. the amount of credits used on following invoices)
                     // is the current account CBA balance (minus the sign)
@@ -802,206 +706,13 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                useExistingCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+                // In theory we should only have to call useExistingCBAFromTransaction but just to be safe we also check for credit generation
+                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
                 return null;
             }
         });
     }
 
-    private void useExistingCBAFromTransaction(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws InvoiceApiException, EntityPersistenceException {
-
-        final BigDecimal accountCBA = getAccountCBAFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
-        if (accountCBA.compareTo(BigDecimal.ZERO) <= 0) {
-            return;
-        }
-
-        final List<InvoiceModelDao> unpaidInvoices = getUnpaidInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, null, context);
-        // We order the same os BillingStateCalculator-- should really share the comparator
-        final List<InvoiceModelDao> orderedUnpaidInvoices = Ordering.from(new Comparator<InvoiceModelDao>() {
-            @Override
-            public int compare(final InvoiceModelDao i1, final InvoiceModelDao i2) {
-                return i1.getInvoiceDate().compareTo(i2.getInvoiceDate());
-            }
-        }).immutableSortedCopy(unpaidInvoices);
-
-        BigDecimal remainingAccountCBA = accountCBA;
-        for (InvoiceModelDao cur : orderedUnpaidInvoices) {
-            final BigDecimal curInvoiceBalance = InvoiceModelDaoHelper.getBalance(cur);
-            final BigDecimal cbaToApplyOnInvoice = remainingAccountCBA.compareTo(curInvoiceBalance) <= 0 ? remainingAccountCBA : curInvoiceBalance;
-            remainingAccountCBA = remainingAccountCBA.subtract(cbaToApplyOnInvoice);
-
-
-            final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ,
-                                                                           cur.getId(), cur.getAccountId(),
-                                                                           null, null, null, null,
-                                                                           context.getCreatedDate().toLocalDate(),
-                                                                           null, cbaToApplyOnInvoice.negate(), null,
-                                                                           cur.getCurrency(), null);
-
-            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-            transInvoiceItemDao.create(cbaAdjItem, context);
-
-            if (remainingAccountCBA.compareTo(BigDecimal.ZERO) <= 0) {
-                break;
-            }
-        }
-
-        // TODO Should we send an event on the bus for Analytics?
-    }
-
-
-    private List<InvoiceModelDao> getUnpaidInvoicesByAccountFromTransaction(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final LocalDate upToDate, final InternalTenantContext context) {
-        final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
-        final Collection<InvoiceModelDao> unpaidInvoices = Collections2.filter(invoices, new Predicate<InvoiceModelDao>() {
-            @Override
-            public boolean apply(final InvoiceModelDao in) {
-                return (InvoiceModelDaoHelper.getBalance(in).compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
-            }
-        });
-        return new ArrayList<InvoiceModelDao>(unpaidInvoices);
-    }
-
-
-    /**
-     * Create an adjustment for a given invoice item. This just creates the object in memory, it doesn't write it to disk.
-     *
-     * @param invoiceId         the invoice id
-     * @param invoiceItemId     the invoice item id to adjust
-     * @param effectiveDate     adjustment effective date, in the account timezone
-     * @param positiveAdjAmount the amount to adjust. Pass null to adjust the full amount of the original item
-     * @param currency          the currency of the amount. Pass null to default to the original currency used
-     * @return the adjustment item
-     */
-    private InvoiceItemModelDao createAdjustmentItem(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID invoiceId, final UUID invoiceItemId,
-                                                     final BigDecimal positiveAdjAmount, final Currency currency,
-                                                     final LocalDate effectiveDate, final InternalCallContext context) throws InvoiceApiException {
-        // First, retrieve the invoice item in question
-        final InvoiceItemSqlDao invoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-        final InvoiceItemModelDao invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString(), context);
-        if (invoiceItemToBeAdjusted == null) {
-            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
-        }
-
-        // Validate the invoice it belongs to
-        if (!invoiceItemToBeAdjusted.getInvoiceId().equals(invoiceId)) {
-            throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT, invoiceItemId, invoiceId);
-        }
-
-        // Retrieve the amount and currency if needed
-        final BigDecimal amountToAdjust = Objects.firstNonNull(positiveAdjAmount, invoiceItemToBeAdjusted.getAmount());
-        // TODO - should we enforce the currency (and respect the original one) here if the amount passed was null?
-        final Currency currencyForAdjustment = Objects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
-
-        // Finally, create the adjustment
-        // Note! The amount is negated here!
-        return new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.ITEM_ADJ, invoiceItemToBeAdjusted.getInvoiceId(), invoiceItemToBeAdjusted.getAccountId(),
-                                       null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
-    }
-
-    /**
-     * Create an invoice item and adjust the invoice with a CBA item if the new invoice balance is negative.
-     *
-     * @param entitySqlDaoWrapperFactory the EntitySqlDaoWrapperFactory from the current transaction
-     * @param item                       the invoice item to create
-     * @param context                    the call context
-     */
-    private void insertItemAndAddCBAIfNeeded(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
-                                             final InvoiceItemModelDao item,
-                                             final InternalCallContext context) throws EntityPersistenceException {
-        final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-        transInvoiceItemDao.create(item, context);
-
-        addCBAIfNeeded(entitySqlDaoWrapperFactory, item.getInvoiceId(), context);
-    }
-
-    /**
-     * Adjust the invoice with a CBA item if the new invoice balance is negative.
-     *
-     * @param entitySqlDaoWrapperFactory the EntitySqlDaoWrapperFactory from the current transaction
-     * @param invoiceId                  the invoice id to adjust
-     * @param context                    the call context
-     */
-    private void addCBAIfNeeded(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
-                                final UUID invoiceId,
-                                final InternalCallContext context) throws EntityPersistenceException {
-        final InvoiceModelDao invoice = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getById(invoiceId.toString(), context);
-        if (invoice != null) {
-            populateChildren(invoice, entitySqlDaoWrapperFactory, context);
-        } else {
-            throw new IllegalStateException("Invoice shouldn't be null for this item at this stage " + invoiceId);
-        }
-
-        // If invoice balance becomes negative we add some CBA item
-        final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
-        if (balance.compareTo(BigDecimal.ZERO) < 0) {
-            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-            final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoice.getId(), invoice.getAccountId(),
-                                                                           null, null, null, null, context.getCreatedDate().toLocalDate(),
-                                                                           null, balance.negate(), null, invoice.getCurrency(), null);
-            transInvoiceItemDao.create(cbaAdjItem, context);
-        }
-    }
-
-    private BigDecimal getAccountCBAFromTransaction(final UUID accountId,
-                                                    final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
-                                                    final InternalTenantContext context) {
-        BigDecimal cba = BigDecimal.ZERO;
-        final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
-        for (final InvoiceModelDao cur : invoices) {
-            final InvoiceItemList invoiceItems = new InvoiceItemList(cur.getInvoiceItems());
-            cba = cba.add(invoiceItems.getCBAAmount());
-        }
-        return cba;
-    }
-
-    private void populateChildren(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        getInvoiceItemsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
-        getInvoicePaymentsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
-    }
-
-    private void populateChildren(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        getInvoiceItemsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
-        getInvoicePaymentsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
-    }
-
-    private List<InvoiceModelDao> getAllInvoicesByAccountFromTransaction(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        final List<InvoiceModelDao> invoices = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getAllInvoicesByAccount(accountId.toString(), context);
-        populateChildren(invoices, entitySqlDaoWrapperFactory, context);
-        return invoices;
-    }
-
-    private BigDecimal getRemainingAmountPaidFromTransaction(final UUID invoicePaymentId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        final BigDecimal amount = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getRemainingAmountPaid(invoicePaymentId.toString(), context);
-        return amount == null ? BigDecimal.ZERO : amount;
-    }
-
-    private void getInvoiceItemsWithinTransaction(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        for (final InvoiceModelDao invoice : invoices) {
-            getInvoiceItemsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
-        }
-    }
-
-    private void getInvoiceItemsWithinTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        final String invoiceId = invoice.getId().toString();
-
-        final InvoiceItemSqlDao transInvoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-        final List<InvoiceItemModelDao> items = transInvoiceItemSqlDao.getInvoiceItemsByInvoice(invoiceId, context);
-        invoice.addInvoiceItems(items);
-    }
-
-    private void getInvoicePaymentsWithinTransaction(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        for (final InvoiceModelDao invoice : invoices) {
-            getInvoicePaymentsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
-        }
-    }
-
-    private void getInvoicePaymentsWithinTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
-        final InvoicePaymentSqlDao invoicePaymentSqlDao = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
-        final String invoiceId = invoice.getId().toString();
-        final List<InvoicePaymentModelDao> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId, context);
-        invoice.addPayments(invoicePayments);
-    }
-
     private void notifyOfFutureBillingEvents(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID accountId,
                                              final Map<UUID, DateTime> callbackDateTimePerSubscriptions, final UUID userToken) {
         for (final UUID subscriptionId : callbackDateTimePerSubscriptions.keySet()) {
@@ -1019,4 +730,5 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
             log.warn("Failed to post adjustment event for invoice " + invoiceId, e);
         }
     }
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
new file mode 100644
index 0000000..641d4f3
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
@@ -0,0 +1,267 @@
+/*
+ * 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.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.entity.EntityPersistenceException;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap.Builder;
+
+public class InvoiceDaoHelper {
+
+
+    /**
+     * Find amounts to adjust for individual items, if not specified.
+     * The user gives us a list of items to adjust associated with a given amount (how much to refund per invoice item).
+     * In case of full adjustments, the amount can be null: in this case, we retrieve the original amount for the invoice
+     * item.
+     *
+     * @param invoiceId                     original invoice id
+     * @param entitySqlDaoWrapperFactory    the EntitySqlDaoWrapperFactory from the current transaction
+     * @param invoiceItemIdsWithNullAmounts the original mapping between invoice item ids and amount to refund (contains null)
+     * @param context                       the tenant context
+     * @return the final mapping between invoice item ids and amount to refund
+     * @throws com.ning.billing.invoice.api.InvoiceApiException
+     *
+     */
+    public Map<UUID, BigDecimal> computeItemAdjustments(final String invoiceId,
+                                                        final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
+                                                        final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts,
+                                                        final InternalTenantContext context) throws InvoiceApiException {
+        // Populate the missing amounts for individual items, if needed
+        final Builder<UUID, BigDecimal> invoiceItemIdsWithAmountsBuilder = new Builder<UUID, BigDecimal>();
+        if (invoiceItemIdsWithNullAmounts.size() == 0) {
+            return invoiceItemIdsWithAmountsBuilder.build();
+        }
+
+        // Retrieve invoice before the Refund
+        final InvoiceModelDao invoice = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getById(invoiceId, context);
+        if (invoice != null) {
+            populateChildren(invoice, entitySqlDaoWrapperFactory, context);
+        } else {
+            throw new IllegalStateException("Invoice shouldn't be null for id " + invoiceId);
+        }
+
+        for (final UUID invoiceItemId : invoiceItemIdsWithNullAmounts.keySet()) {
+            final BigDecimal adjAmount = Objects.firstNonNull(invoiceItemIdsWithNullAmounts.get(invoiceItemId),
+                                                              getInvoiceItemAmountForId(invoice, invoiceItemId));
+            final BigDecimal adjAmountRemainingAfterRepair = computeItemAdjustmentAmount(invoiceItemId, adjAmount, invoice.getInvoiceItems());
+            if (adjAmountRemainingAfterRepair.compareTo(BigDecimal.ZERO) > 0) {
+                invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, adjAmountRemainingAfterRepair);
+            }
+        }
+
+        return invoiceItemIdsWithAmountsBuilder.build();
+    }
+
+    /**
+     * @param invoiceItem  item we are adjusting
+     * @param requestedPositiveAmountToAdjust
+     *                     amount we are adjusting for that item
+     * @param invoiceItems list of all invoice items on this invoice
+     * @return the amount we should really adjust based on whether or not the item got repaired
+     */
+    private BigDecimal computeItemAdjustmentAmount(final UUID invoiceItem, final BigDecimal requestedPositiveAmountToAdjust, final List<InvoiceItemModelDao> invoiceItems) {
+
+        BigDecimal positiveRepairedAmount = BigDecimal.ZERO;
+
+        final Collection<InvoiceItemModelDao> repairedItems = Collections2.filter(invoiceItems, new Predicate<InvoiceItemModelDao>() {
+            @Override
+            public boolean apply(final InvoiceItemModelDao input) {
+                return (input.getType() == InvoiceItemType.REPAIR_ADJ && input.getLinkedItemId().equals(invoiceItem));
+            }
+        });
+        for (final InvoiceItemModelDao cur : repairedItems) {
+            // Repair item are negative so we negate to make it positive
+            positiveRepairedAmount = positiveRepairedAmount.add(cur.getAmount().negate());
+        }
+        return (positiveRepairedAmount.compareTo(requestedPositiveAmountToAdjust) >= 0) ? BigDecimal.ZERO : requestedPositiveAmountToAdjust.subtract(positiveRepairedAmount);
+    }
+
+    private BigDecimal getInvoiceItemAmountForId(final InvoiceModelDao invoice, final UUID invoiceItemId) throws InvoiceApiException {
+        for (final InvoiceItemModelDao invoiceItem : invoice.getInvoiceItems()) {
+            if (invoiceItem.getId().equals(invoiceItemId)) {
+                return invoiceItem.getAmount();
+            }
+        }
+
+        throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+    }
+
+    public BigDecimal computePositiveRefundAmount(final InvoicePaymentModelDao payment, final BigDecimal requestedAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
+        final BigDecimal maxRefundAmount = payment.getAmount() == null ? BigDecimal.ZERO : payment.getAmount();
+        final BigDecimal requestedPositiveAmount = requestedAmount == null ? maxRefundAmount : requestedAmount;
+        // This check is good but not enough, we need to also take into account previous refunds
+        // (But that should have been checked in the payment call already)
+        if (requestedPositiveAmount.compareTo(maxRefundAmount) > 0) {
+            throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_TOO_HIGH, requestedPositiveAmount, maxRefundAmount);
+        }
+
+        // Verify if the requested amount matches the invoice items to adjust, if specified
+        BigDecimal amountFromItems = BigDecimal.ZERO;
+        for (final BigDecimal itemAmount : invoiceItemIdsWithAmounts.values()) {
+            amountFromItems = amountFromItems.add(itemAmount);
+        }
+
+        // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
+        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && requestedPositiveAmount.compareTo(amountFromItems) != 0) {
+            throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST, requestedPositiveAmount, amountFromItems);
+        }
+        return requestedPositiveAmount;
+    }
+
+
+    public List<InvoiceModelDao> getUnpaidInvoicesByAccountFromTransaction(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final LocalDate upToDate, final InternalTenantContext context) {
+        final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(accountId, entitySqlDaoWrapperFactory, context);
+        return getUnpaidInvoicesByAccountFromTransaction(invoices, upToDate);
+    }
+
+
+    public List<InvoiceModelDao> getUnpaidInvoicesByAccountFromTransaction(final List<InvoiceModelDao> invoices, @Nullable final LocalDate upToDate) {
+        final Collection<InvoiceModelDao> unpaidInvoices = Collections2.filter(invoices, new Predicate<InvoiceModelDao>() {
+            @Override
+            public boolean apply(final InvoiceModelDao in) {
+                return (InvoiceModelDaoHelper.getBalance(in).compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
+            }
+        });
+        return new ArrayList<InvoiceModelDao>(unpaidInvoices);
+
+    }
+
+    /**
+     * Create an adjustment for a given invoice item. This just creates the object in memory, it doesn't write it to disk.
+     *
+     * @param invoiceId         the invoice id
+     * @param invoiceItemId     the invoice item id to adjust
+     * @param effectiveDate     adjustment effective date, in the account timezone
+     * @param positiveAdjAmount the amount to adjust. Pass null to adjust the full amount of the original item
+     * @param currency          the currency of the amount. Pass null to default to the original currency used
+     * @return the adjustment item
+     */
+    public InvoiceItemModelDao createAdjustmentItem(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID invoiceId, final UUID invoiceItemId,
+                                                    final BigDecimal positiveAdjAmount, final Currency currency,
+                                                    final LocalDate effectiveDate, final InternalCallContext context) throws InvoiceApiException {
+        // First, retrieve the invoice item in question
+        final InvoiceItemSqlDao invoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+        final InvoiceItemModelDao invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString(), context);
+        if (invoiceItemToBeAdjusted == null) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+        }
+
+        // Validate the invoice it belongs to
+        if (!invoiceItemToBeAdjusted.getInvoiceId().equals(invoiceId)) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT, invoiceItemId, invoiceId);
+        }
+
+        // Retrieve the amount and currency if needed
+        final BigDecimal amountToAdjust = Objects.firstNonNull(positiveAdjAmount, invoiceItemToBeAdjusted.getAmount());
+        // TODO - should we enforce the currency (and respect the original one) here if the amount passed was null?
+        final Currency currencyForAdjustment = Objects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
+
+        // Finally, create the adjustment
+        // Note! The amount is negated here!
+        return new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.ITEM_ADJ, invoiceItemToBeAdjusted.getInvoiceId(), invoiceItemToBeAdjusted.getAccountId(),
+                                       null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
+    }
+
+    /**
+     * Create an invoice item
+     *
+     * @param entitySqlDaoWrapperFactory the EntitySqlDaoWrapperFactory from the current transaction
+     * @param item                       the invoice item to create
+     * @param context                    the call context
+     */
+    public void insertItem(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
+                           final InvoiceItemModelDao item,
+                           final InternalCallContext context) throws EntityPersistenceException {
+        final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+        transInvoiceItemDao.create(item, context);
+    }
+
+
+    public void populateChildren(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        getInvoiceItemsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
+        getInvoicePaymentsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
+    }
+
+    public void populateChildren(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        getInvoiceItemsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
+        getInvoicePaymentsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
+    }
+
+    public List<InvoiceModelDao> getAllInvoicesByAccountFromTransaction(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        final List<InvoiceModelDao> invoices = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getAllInvoicesByAccount(accountId.toString(), context);
+        populateChildren(invoices, entitySqlDaoWrapperFactory, context);
+        return invoices;
+    }
+
+    public BigDecimal getRemainingAmountPaidFromTransaction(final UUID invoicePaymentId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        final BigDecimal amount = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getRemainingAmountPaid(invoicePaymentId.toString(), context);
+        return amount == null ? BigDecimal.ZERO : amount;
+    }
+
+    private void getInvoiceItemsWithinTransaction(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        for (final InvoiceModelDao invoice : invoices) {
+            getInvoiceItemsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
+        }
+    }
+
+    private void getInvoiceItemsWithinTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        final String invoiceId = invoice.getId().toString();
+
+        final InvoiceItemSqlDao transInvoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+        final List<InvoiceItemModelDao> items = transInvoiceItemSqlDao.getInvoiceItemsByInvoice(invoiceId, context);
+        invoice.addInvoiceItems(items);
+    }
+
+    private void getInvoicePaymentsWithinTransaction(final List<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        for (final InvoiceModelDao invoice : invoices) {
+            getInvoicePaymentsWithinTransaction(invoice, entitySqlDaoWrapperFactory, context);
+        }
+    }
+
+    private void getInvoicePaymentsWithinTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+        final InvoicePaymentSqlDao invoicePaymentSqlDao = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
+        final String invoiceId = invoice.getId().toString();
+        final List<InvoicePaymentModelDao> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId, context);
+        invoice.addPayments(invoicePayments);
+    }
+
+
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 1c9a78a..19c14f2 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -122,9 +122,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         // TODO Should this be merged with existing CBA logic in the dao layer ?
         generateCBAForExistingInvoices(accountId, existingInvoices, proposedItems, targetCurrency);
 
-
-        consumeExistingCredit(invoiceId, accountId, existingItems, proposedItems, targetCurrency);
-
         // Finally add thos new items on the new invoice
         invoice.addInvoiceItems(proposedItems);
 
@@ -203,42 +200,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         return totalAdjustedOnItem.negate();
     }
 
-    void consumeExistingCredit(final UUID invoiceId, final UUID accountId, final List<InvoiceItem> existingItems,
-                               final List<InvoiceItem> proposedItems, final Currency targetCurrency) {
-
-        BigDecimal totalUnusedCreditAmount = BigDecimal.ZERO;
-        BigDecimal totalAmountOwed = BigDecimal.ZERO;
-
-        for (final InvoiceItem item : existingItems) {
-            if (item.getInvoiceItemType() == InvoiceItemType.CBA_ADJ) {
-                totalUnusedCreditAmount = totalUnusedCreditAmount.add(item.getAmount());
-            }
-        }
-
-        for (final InvoiceItem item : proposedItems) {
-            if (item.getInvoiceItemType() == InvoiceItemType.CBA_ADJ) {
-                totalUnusedCreditAmount = totalUnusedCreditAmount.add(item.getAmount());
-            } else if (item.getInvoiceId().equals(invoiceId)) {
-                totalAmountOwed = totalAmountOwed.add(item.getAmount());
-            }
-        }
-
-        BigDecimal creditAmount = BigDecimal.ZERO;
-        if (totalUnusedCreditAmount.compareTo(BigDecimal.ZERO) > 0) {
-            if (totalAmountOwed.abs().compareTo(totalUnusedCreditAmount.abs()) > 0) {
-                creditAmount = totalUnusedCreditAmount.negate();
-            } else {
-                creditAmount = totalAmountOwed.negate();
-            }
-        }
-
-        if (creditAmount.compareTo(BigDecimal.ZERO) < 0) {
-            final LocalDate creditDate = clock.getUTCToday();
-            final CreditBalanceAdjInvoiceItem creditInvoiceItem = new CreditBalanceAdjInvoiceItem(invoiceId, accountId, creditDate, creditAmount, targetCurrency);
-            proposedItems.add(creditInvoiceItem);
-        }
-    }
-
     private void validateTargetDate(final LocalDate targetDate) throws InvoiceApiException {
         final int maximumNumberOfMonths = config.getNumberOfMonthsInFuture();
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDaoUnit.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDaoUnit.java
index cc89e99..344e9e6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDaoUnit.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDaoUnit.java
@@ -77,7 +77,8 @@ public class TestDefaultInvoiceDaoUnit extends InvoiceTestSuiteNoDB {
         final InvoicePaymentModelDao invoicePayment = Mockito.mock(InvoicePaymentModelDao.class);
         Mockito.when(invoicePayment.getAmount()).thenReturn(paymentAmount);
 
-        final BigDecimal actualRefundAmount = DefaultInvoiceDao.computePositiveRefundAmount(invoicePayment, requestedAmount, invoiceItemIdsWithAmounts);
+        final InvoiceDaoHelper invoiceDaoHelper = new InvoiceDaoHelper();
+        final BigDecimal actualRefundAmount = invoiceDaoHelper.computePositiveRefundAmount(invoicePayment, requestedAmount, invoiceItemIdsWithAmounts);
         Assert.assertEquals(actualRefundAmount, expectedRefundAmount);
     }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 0fc291f..d7a337a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -1004,49 +1004,6 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         assertTrue(invoice3.getBalance().compareTo(FIFTEEN.multiply(TWO).add(TWELVE)) == 0);
     }
 
-    @Test(groups = "fast")
-    public void testAccountCredit() throws CatalogApiException, InvoiceApiException {
-        final BillingEventSet billingEventSet = new MockBillingEventSet();
-
-        final LocalDate startDate = new LocalDate(2012, 3, 1);
-        final UUID accountId = UUID.randomUUID();
-        final UUID subscriptionId = UUID.randomUUID();
-        final Plan plan = new MockPlan("original plan");
-        final MockInternationalPrice price10 = new MockInternationalPrice(new DefaultPrice(TEN, Currency.USD));
-        final PlanPhase planPhase = new MockPlanPhase(price10, null, BillingPeriod.MONTHLY, PhaseType.EVERGREEN);
-        final BillingEvent creation = createBillingEvent(subscriptionId, startDate, plan, planPhase, 1);
-        billingEventSet.add(creation);
-
-        final List<Invoice> invoices = new ArrayList<Invoice>();
-
-        final Invoice initialInvoice = generator.generateInvoice(accountId, billingEventSet, null, startDate, Currency.USD);
-        assertNotNull(initialInvoice);
-        assertEquals(initialInvoice.getNumberOfItems(), 1);
-        assertEquals(initialInvoice.getBalance().compareTo(TEN), 0);
-        invoices.add(initialInvoice);
-
-        printDetailInvoice(initialInvoice);
-
-        // add account-level credit
-        final LocalDate creditDate = new LocalDate(startDate.plusDays(5), DateTimeZone.UTC);
-        final Invoice invoiceWithCredit = new DefaultInvoice(accountId, creditDate, creditDate, Currency.USD);
-        final InvoiceItem accountCredit = new CreditBalanceAdjInvoiceItem(invoiceWithCredit.getId(), accountId, creditDate, FIVE, Currency.USD);
-        invoiceWithCredit.addInvoiceItem(accountCredit);
-        invoices.add(invoiceWithCredit);
-
-        printDetailInvoice(invoiceWithCredit);
-
-        // invoice one month after the initial subscription
-        final Invoice finalInvoice = generator.generateInvoice(accountId, billingEventSet, invoices, startDate.plusMonths(1), Currency.USD);
-
-        printDetailInvoice(finalInvoice);
-
-        System.out.println("BALANCE = " + finalInvoice.getBalance());
-        assertEquals(finalInvoice.getBalance().compareTo(FIVE), 0);
-        System.out.println("CBA = " + finalInvoice.getCBAAmount());
-        assertEquals(finalInvoice.getCBAAmount().compareTo(FIVE.negate()), 0);
-        assertEquals(finalInvoice.getNumberOfItems(), 2);
-    }
 
     private void printDetailInvoice(final Invoice invoice) {
         log.info("--------------------  START DETAIL ----------------------");
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java
index 5a80d68..e97609d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorUnit.java
@@ -308,58 +308,4 @@ public class TestDefaultInvoiceGeneratorUnit extends InvoiceTestSuiteNoDB {
         assertEquals(creditItemCheck.getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
         assertEquals(creditItemCheck.getAmount(), amount2.add(rate1.negate()));
     }
-
-    @Test(groups = "fast")
-    public void testConsumeNotEnoughExistingCredit() {
-        testConsumeCreditInternal(new BigDecimal("12.00"), new BigDecimal("-10.00"));
-    }
-
-    @Test(groups = "fast")
-    public void testConsumeTooMuchExistingCredit() {
-        testConsumeCreditInternal(new BigDecimal("7.00"), new BigDecimal("-7.00"));
-    }
-
-    private void testConsumeCreditInternal(final BigDecimal newRate, final BigDecimal expectedNewCba) {
-        final LocalDate startDate = clock.getUTCToday();
-        final LocalDate endDate = startDate.plusDays(30);
-        final LocalDate nextEndDate = startDate.plusMonths(1);
-
-        final BigDecimal rate1 = new BigDecimal("20.00");
-        final BigDecimal amount1 = rate1;
-
-        final BigDecimal rate2 = new BigDecimal("10.00");
-        final BigDecimal amount2 = rate2;
-
-        final UUID firstInvoiceId = UUID.randomUUID();
-        final List<InvoiceItem> existing = new LinkedList<InvoiceItem>();
-        final BigDecimal pcba1 = new BigDecimal("10.00");
-
-        final InvoiceItem item1 = new RecurringInvoiceItem(firstInvoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
-        final InvoiceItem reversedItem1 = new RepairAdjInvoiceItem(firstInvoiceId, accountId, startDate, nextEndDate, amount1.negate(), currency, item1.getId());
-        final InvoiceItem newItem1 = new RecurringInvoiceItem(firstInvoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount2, rate2, currency);
-        final InvoiceItem cba1 = new CreditBalanceAdjInvoiceItem(firstInvoiceId, accountId, startDate, pcba1, currency);
-        existing.add(item1);
-        existing.add(reversedItem1);
-        existing.add(newItem1);
-        existing.add(cba1);
-
-        final BigDecimal newRate2 = newRate;
-        final BigDecimal newAmount2 = newRate2;
-
-        final List<InvoiceItem> proposed = new LinkedList<InvoiceItem>();
-        final InvoiceItem item2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate.plusMonths(1), endDate.plusMonths(1), newAmount2, newRate2, currency);
-        proposed.add(item2);
-
-        ((DefaultInvoiceGenerator) generator).consumeExistingCredit(invoiceId, firstInvoiceId, existing, proposed, currency);
-        assertEquals(proposed.size(), 2);
-        final InvoiceItem item2Check = proposed.get(0);
-        assertEquals(item2Check.getInvoiceId(), invoiceId);
-        assertEquals(item2Check.getInvoiceItemType(), InvoiceItemType.RECURRING);
-        assertEquals(item2Check.getAmount(), newAmount2);
-
-        final InvoiceItem cbaCheck = proposed.get(1);
-        assertEquals(cbaCheck.getInvoiceId(), invoiceId);
-        assertEquals(cbaCheck.getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
-        assertEquals(cbaCheck.getAmount(), expectedNewCba);
-    }
 }