killbill-uncached

Initial work for #152

10/10/2014 8:21:45 PM

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
index 82848fc..b4e42b8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
@@ -29,6 +29,7 @@ import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Ordering;
 
 public class CBADao {
@@ -55,7 +56,48 @@ public class CBADao {
         return cba;
     }
 
-    public void doCBAComplexity(final UUID accountId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+    public InvoiceItemModelDao computeCBAComplexity(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+
+        final BigDecimal balance = InvoiceModelDaoHelper.getBalance(invoice);
+
+        // Current balance is negative, we need to generate a credit (positive CBA amount).
+        if (balance.compareTo(BigDecimal.ZERO) < 0) {
+            return new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), balance.negate(), invoice.getCurrency()));
+
+        // Current balance is positive, we need to use some of the existing if available (negative CBA amount)
+        } else if (balance.compareTo(BigDecimal.ZERO) > 0) {
+
+            final List<InvoiceModelDao> allInvoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
+            final BigDecimal accountCBA = getAccountCBAFromTransaction(allInvoices);
+            if (accountCBA.compareTo(BigDecimal.ZERO) <= 0) {
+                return null;
+            }
+            final BigDecimal positiveCreditAmount = accountCBA.compareTo(balance) > 0 ? balance : accountCBA;
+            return new InvoiceItemModelDao(new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), positiveCreditAmount.negate(), invoice.getCurrency()));
+        } else {
+            // 0 balance, nothing to do.
+            return null;
+        }
+    }
+
+
+    public void addCBAComplexityFromTransaction(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+        final InvoiceItemModelDao cbaItem = computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context);
+        if (cbaItem != null) {
+            final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+            transInvoiceItemDao.create(cbaItem, context);
+        }
+    }
+
+    public void addCBAComplexityFromTransaction(final UUID invoiceId, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
+
+        final InvoiceSqlDao transInvoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
+        final InvoiceModelDao invoice = transInvoiceDao.getById(invoiceId.toString(), context);
+        invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
+        addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
+    }
+
+    public void addCBAComplexityFromTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final InternalCallContext context) throws EntityPersistenceException, InvoiceApiException {
 
         List<InvoiceModelDao> invoiceItemModelDaos = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
         for (InvoiceModelDao cur : invoiceItemModelDaos) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 1f574e0..14105d5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -205,6 +205,10 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
+    //
+    // Note that we expect CBA complexity to be run outside of invoice, so provided list of invoiceItems may already contain
+    // some CBA adjustments and there is no calls to CBA complexity.
+    //
     @Override
     public void createInvoice(final InvoiceModelDao invoice, final List<InvoiceItemModelDao> invoiceItems,
                               final boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions,
@@ -227,9 +231,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     for (final InvoiceItemModelDao invoiceItemModelDao : invoiceItems) {
                         transInvoiceItemSqlDao.create(invoiceItemModelDao, context);
                     }
-
-                    cbaDao.doCBAComplexity(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
-
                     notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoice.getAccountId(), callbackDateTimePerSubscriptions, context.getUserToken());
                 }
                 return null;
@@ -401,11 +402,8 @@ 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) {
-                    invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
-                } else {
-                    throw new IllegalStateException("Invoice shouldn't be null for payment " + payment.getId());
-                }
+                Preconditions.checkState(invoice !=  null, "Invoice shouldn't be null for payment " + payment.getId());
+                invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
 
                 final BigDecimal invoiceBalanceAfterRefund = InvoiceModelDaoHelper.getBalance(invoice);
                 final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
@@ -422,6 +420,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                                                                     null, null, null, null, null, null, context.getCreatedDate().toLocalDate(), null,
                                                                                     requestedPositiveAmountToAdjust.negate(), null, invoice.getCurrency(), null);
                         transInvoiceItemDao.create(adjItem, context);
+                        invoice.addInvoiceItem(adjItem);
                     }
                 } else if (isInvoiceAdjusted) {
                     // Invoice item adjustment
@@ -431,10 +430,11 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                                                                                invoice.getCurrency(), context.getCreatedDate().toLocalDate(),
                                                                                                context);
                         transInvoiceItemDao.create(item, context);
+                        invoice.addInvoiceItem(item);
                     }
                 }
 
-                cbaDao.doCBAComplexity(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
+                cbaDao.addCBAComplexityFromTransaction(invoice, entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoice.getId(), invoice.getAccountId(), context.getUserToken(), context);
@@ -488,7 +488,7 @@ 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);
 
-                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
+                cbaDao.addCBAComplexityFromTransaction(payment.getInvoiceId(), entitySqlDaoWrapperFactory, context);
 
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, payment.getInvoiceId(), accountId, context.getUserToken(), context);
 
@@ -498,6 +498,18 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
+    public InvoiceItemModelDao doCBAComplexity(final InvoiceModelDao invoice, final InternalCallContext context) throws InvoiceApiException {
+        return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoiceItemModelDao>() {
+            @Override
+            public InvoiceItemModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final InvoiceItemModelDao cbaNewItem = cbaDao.computeCBAComplexity(invoice, entitySqlDaoWrapperFactory, context);
+                return cbaNewItem;
+            }
+        });
+    }
+
+
+    @Override
     public BigDecimal getRemainingAmountPaid(final UUID invoicePaymentId, final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<BigDecimal>() {
             @Override
@@ -643,7 +655,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     changedInvoices.add(invoiceIdForExternalCharge);
                 }
 
-                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
+                cbaDao.addCBAComplexityFromTransaction(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)
@@ -694,7 +706,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                                                            currency, null);
                 invoiceDaoHelper.insertItem(entitySqlDaoWrapperFactory, credit, context);
 
-                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
+                cbaDao.addCBAComplexityFromTransaction(invoiceIdForCredit, entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
@@ -715,7 +727,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                                                                                                         currency, effectiveDate, context);
                 invoiceDaoHelper.insertItem(entitySqlDaoWrapperFactory, invoiceItemAdjustment, context);
 
-                cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
+                cbaDao.addCBAComplexityFromTransaction(invoiceId, entitySqlDaoWrapperFactory, context);
 
                 notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
 
@@ -822,7 +834,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
                 // 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);
+                cbaDao.addCBAComplexityFromTransaction(entitySqlDaoWrapperFactory, context);
                 return null;
             }
         });
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
index 69cdf91..b350b42 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
@@ -28,10 +28,13 @@ import org.joda.time.LocalDate;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.EntityDao;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
 public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceApiException> {
 
@@ -66,18 +69,20 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
 
     InvoicePaymentModelDao postChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
 
-    /**
-     * Create a refund.
-     *
-     * @param paymentId                 payment associated with that refund
-     * @param amount                    amount to refund
-     * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
-     * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
-     * @param transactionExternalKey    transaction refund externalKey
-     * @param context                   the call callcontext
-     * @return the created invoice payment object associated with this refund
-     * @throws InvoiceApiException
-     */
+    InvoiceItemModelDao doCBAComplexity(InvoiceModelDao invoice, InternalCallContext context) throws InvoiceApiException;
+
+        /**
+         * Create a refund.
+         *
+         * @param paymentId                 payment associated with that refund
+         * @param amount                    amount to refund
+         * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
+         * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
+         * @param transactionExternalKey    transaction refund externalKey
+         * @param context                   the call callcontext
+         * @return the created invoice payment object associated with this refund
+         * @throws InvoiceApiException
+         */
     InvoicePaymentModelDao createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
                                         String transactionExternalKey, InternalCallContext context) throws InvoiceApiException;
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
index ae3cce6..cd7aa45 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
@@ -77,6 +77,10 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         this.invoiceItems.addAll(invoiceItems);
     }
 
+    public void addInvoiceItem(final InvoiceItemModelDao invoiceItem) {
+        this.invoiceItems.add(invoiceItem);
+    }
+
     public List<InvoiceItemModelDao> getInvoiceItems() {
         return invoiceItems;
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 03449af..edc9b8a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -51,17 +51,16 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoiceNotifier;
-import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
 import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
 import org.killbill.billing.invoice.api.user.DefaultNullInvoiceEvent;
 import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
-import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
 import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
+import org.killbill.billing.invoice.model.InvoiceItemFactory;
 import org.killbill.billing.invoice.model.RecurringInvoiceItem;
 import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
 import org.killbill.billing.junction.BillingEventSet;
@@ -203,7 +202,9 @@ public class InvoiceDispatcher {
 
             final LocalDate targetDate = dateAndTimeZoneContext != null ? dateAndTimeZoneContext.computeTargetDate(targetDateTime) : null;
             final Invoice invoice = targetDate != null ? generator.generateInvoice(accountId, billingEvents, invoices, targetDate, targetCurrency, context) : null;
-            boolean isRealInvoiceWithItems = false;
+            //
+            // If invoice comes back null, there is nothing new to generate, we can bail early
+            //
             if (invoice == null) {
                 log.info("Generated null invoice for accountId {} and targetDate {} (targetDateTime {})", new Object[]{accountId, targetDate, targetDateTime});
                 if (!dryRun) {
@@ -211,75 +212,87 @@ public class InvoiceDispatcher {
                                                                                context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
                     postEvent(event, accountId, context);
                 }
-            } else {
-                if (!dryRun) {
-                    // Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
-                    final List<InvoicePluginApi> invoicePlugins = this.getInvoicePlugins();
-                    final CallContext callContext = buildCallContext(context);
-                    for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
-                        final List<InvoiceItem> items = invoicePlugin.getAdditionalInvoiceItems(invoice, ImmutableList.<PluginProperty>of(), callContext);
-                        if (items != null) {
-                            for (final InvoiceItem item : items) {
-                                if (InvoiceItemType.EXTERNAL_CHARGE.equals(item.getInvoiceItemType()) || InvoiceItemType.TAX.equals(item.getInvoiceItemType())) {
-                                    invoice.addInvoiceItem(item);
-                                } else {
-                                    log.warn("Ignoring invoice item of type {} from InvoicePluginApi {}: {}", item.getInvoiceItemType(), invoicePlugin, item);
-                                }
-                            }
-                        }
-                    }
+                return invoice;
+            }
+
+            // Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin
+            final InvoiceItem cbaItem = computeCBAOnExistingInvoice(invoice, context);
+            if (cbaItem != null) {
+                invoice.addInvoiceItem(cbaItem);
+            }
 
-                    // Extract the set of invoiceId for which we see items that don't belong to current generated invoice
-                    final Set<UUID> adjustedUniqueOtherInvoiceId = new TreeSet<UUID>();
-                    adjustedUniqueOtherInvoiceId.addAll(Collections2.transform(invoice.getInvoiceItems(), new Function<InvoiceItem, UUID>() {
-                        @Nullable
-                        @Override
-                        public UUID apply(@Nullable final InvoiceItem input) {
-                            return input.getInvoiceId();
+            //
+            // Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
+            //
+            final List<InvoicePluginApi> invoicePlugins = this.getInvoicePlugins();
+            final CallContext callContext = buildCallContext(context);
+            for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
+                final List<InvoiceItem> items = invoicePlugin.getAdditionalInvoiceItems(invoice, ImmutableList.<PluginProperty>of(), callContext);
+                if (items != null) {
+                    for (final InvoiceItem item : items) {
+                        if (InvoiceItemType.EXTERNAL_CHARGE.equals(item.getInvoiceItemType()) || InvoiceItemType.TAX.equals(item.getInvoiceItemType())) {
+                            invoice.addInvoiceItem(item);
+                        } else {
+                            log.warn("Ignoring invoice item of type {} from InvoicePluginApi {}: {}", item.getInvoiceItemType(), invoicePlugin, item);
                         }
-                    }));
-                    isRealInvoiceWithItems = adjustedUniqueOtherInvoiceId.remove(invoice.getId());
-
-                    if (isRealInvoiceWithItems) {
-                        log.info("Generated invoice {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{invoice.getId(), invoice.getNumberOfItems(), accountId, targetDate, targetDateTime});
-                    } else {
-                        final Joiner joiner = Joiner.on(",");
-                        final String adjustedInvoices = joiner.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
-                        log.info("Adjusting existing invoices {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{adjustedInvoices, invoice.getNumberOfItems(),
-                                                                                                                                                     accountId, targetDate, targetDateTime});
                     }
+                }
+            }
 
-                    final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
-                    final List<InvoiceItemModelDao> invoiceItemModelDaos = ImmutableList.<InvoiceItemModelDao>copyOf(Collections2.transform(invoice.getInvoiceItems(),
-                                                                                                                                            new Function<InvoiceItem, InvoiceItemModelDao>() {
-                                                                                                                                                @Override
-                                                                                                                                                public InvoiceItemModelDao apply(final InvoiceItem input) {
-                                                                                                                                                    return new InvoiceItemModelDao(input);
-                                                                                                                                                }
-                                                                                                                                            }));
-
-                    final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions = createNextFutureNotificationDate(invoiceItemModelDaos, billingEvents.getUsages(), dateAndTimeZoneContext);
-                    invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, isRealInvoiceWithItems, callbackDateTimePerSubscriptions, context);
-
-                    final List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
-                    final List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
-                    setChargedThroughDates(dateAndTimeZoneContext, fixedPriceInvoiceItems, recurringInvoiceItems, context);
-
-                    final List<InvoiceInternalEvent> events = new ArrayList<InvoiceInternalEvent>();
-                    if (isRealInvoiceWithItems) {
-                        events.add(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
-                                                                   invoice.getBalance(), invoice.getCurrency(),
-                                                                   context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
-                    }
-                    for (final UUID cur : adjustedUniqueOtherInvoiceId) {
-                        final InvoiceAdjustmentInternalEvent event = new DefaultInvoiceAdjustmentEvent(cur, invoice.getAccountId(),
-                                                                                                       context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
-                        events.add(event);
+            boolean isRealInvoiceWithItems = false;
+            if (!dryRun) {
+
+                // Extract the set of invoiceId for which we see items that don't belong to current generated invoice
+                final Set<UUID> adjustedUniqueOtherInvoiceId = new TreeSet<UUID>();
+                adjustedUniqueOtherInvoiceId.addAll(Collections2.transform(invoice.getInvoiceItems(), new Function<InvoiceItem, UUID>() {
+                    @Nullable
+                    @Override
+                    public UUID apply(@Nullable final InvoiceItem input) {
+                        return input.getInvoiceId();
                     }
+                }));
+                isRealInvoiceWithItems = adjustedUniqueOtherInvoiceId.remove(invoice.getId());
+
+                if (isRealInvoiceWithItems) {
+                    log.info("Generated invoice {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{invoice.getId(), invoice.getNumberOfItems(), accountId, targetDate, targetDateTime});
+                } else {
+                    final Joiner joiner = Joiner.on(",");
+                    final String adjustedInvoices = joiner.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
+                    log.info("Adjusting existing invoices {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{adjustedInvoices, invoice.getNumberOfItems(),
+                                                                                                                                                 accountId, targetDate, targetDateTime});
+                }
 
-                    for (final InvoiceInternalEvent event : events) {
-                        postEvent(event, accountId, context);
-                    }
+                // Transformation to Invoice -> InvoiceModelDao
+                final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
+                final List<InvoiceItemModelDao> invoiceItemModelDaos = ImmutableList.copyOf(Collections2.transform(invoice.getInvoiceItems(),
+                                                                                                                   new Function<InvoiceItem, InvoiceItemModelDao>() {
+                                                                                                                       @Override
+                                                                                                                       public InvoiceItemModelDao apply(final InvoiceItem input) {
+                                                                                                                           return new InvoiceItemModelDao(input);
+                                                                                                                       }
+                                                                                                                   }));
+
+                final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions = createNextFutureNotificationDate(invoiceItemModelDaos, billingEvents.getUsages(), dateAndTimeZoneContext);
+                invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, isRealInvoiceWithItems, callbackDateTimePerSubscriptions, context);
+
+                final List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
+                final List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
+                setChargedThroughDates(dateAndTimeZoneContext, fixedPriceInvoiceItems, recurringInvoiceItems, context);
+
+                final List<InvoiceInternalEvent> events = new ArrayList<InvoiceInternalEvent>();
+                if (isRealInvoiceWithItems) {
+                    events.add(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
+                                                               invoice.getBalance(), invoice.getCurrency(),
+                                                               context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
+                }
+                for (final UUID cur : adjustedUniqueOtherInvoiceId) {
+                    final InvoiceAdjustmentInternalEvent event = new DefaultInvoiceAdjustmentEvent(cur, invoice.getAccountId(),
+                                                                                                   context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+                    events.add(event);
+                }
+
+                for (final InvoiceInternalEvent event : events) {
+                    postEvent(event, accountId, context);
                 }
             }
 
@@ -296,6 +309,21 @@ public class InvoiceDispatcher {
         }
     }
 
+    private InvoiceItem computeCBAOnExistingInvoice(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException {
+        // Transformation to Invoice -> InvoiceModelDao
+        final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
+        final List<InvoiceItemModelDao> invoiceItemModelDaos = ImmutableList.copyOf(Collections2.transform(invoice.getInvoiceItems(),
+                                                                                                           new Function<InvoiceItem, InvoiceItemModelDao>() {
+                                                                                                               @Override
+                                                                                                               public InvoiceItemModelDao apply(final InvoiceItem input) {
+                                                                                                                   return new InvoiceItemModelDao(input);
+                                                                                                               }
+                                                                                                           }));
+        invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
+        final InvoiceItemModelDao cbaItem = invoiceDao.doCBAComplexity(invoiceModelDao, context);
+        return cbaItem != null ? InvoiceItemFactory.fromModelDao(cbaItem) : null;
+    }
+
     private TenantContext buildTenantContext(final InternalTenantContext context) {
         return context.toTenantContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID)));
     }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index 98e1e20..db40324 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -269,6 +269,12 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
+    public InvoiceItemModelDao doCBAComplexity(final InvoiceModelDao invoice, final InternalCallContext context) throws InvoiceApiException {
+        // Do nothing unless we need it..
+        return null;
+    }
+
+    @Override
     public BigDecimal getRemainingAmountPaid(final UUID invoicePaymentId, final InternalTenantContext context) {
         throw new UnsupportedOperationException();
     }