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();
}