Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
index ef0a637..a0d22f9 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
@@ -787,7 +787,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
BigDecimal parentAccountCBA = invoiceUserApi.getAccountCBA(parentAccount.getId(), callContext);
assertEquals(parentAccountCBA.compareTo(BigDecimal.ZERO), 0);
- busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE);
invoiceUserApi.transferChildCreditToParent(childAccount.getId(), callContext);
assertListenerStatus();
@@ -812,13 +812,11 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(parentInvoices.size(), 1);
final Invoice parentInvoice = parentInvoices.get(0);
- assertEquals(parentInvoice.getNumberOfItems(), 3);
+ assertEquals(parentInvoice.getNumberOfItems(), 2);
assertEquals(parentInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
assertEquals(parentInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.valueOf(-250)), 0);
assertEquals(parentInvoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
assertEquals(parentInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(250)), 0);
- assertEquals(parentInvoice.getInvoiceItems().get(2).getInvoiceItemType(), InvoiceItemType.PARENT_SUMMARY);
- assertEquals(parentInvoice.getInvoiceItems().get(2).getAmount().compareTo(BigDecimal.ZERO), 0);
}
// Scenario 6-b: Transfer credit
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 10d3f5f..d9cbe1c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -534,12 +534,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
- public void transferChildCreditToParent(final UUID childAccountId, final CallContext callContext) throws InvoiceApiException {
+ public void transferChildCreditToParent(final UUID childAccountId, final CallContext context) throws InvoiceApiException {
final Account childAccount;
- final InternalTenantContext internalContext = internalCallContextFactory.createInternalTenantContext(childAccountId, ObjectType.INVOICE, callContext);
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(childAccountId, ObjectType.ACCOUNT, context);
try {
- childAccount = accountUserApi.getAccountById(childAccountId, internalContext);
+ childAccount = accountUserApi.getAccountById(childAccountId, internalCallContext);
} catch (AccountApiException e) {
throw new InvoiceApiException(e);
}
@@ -548,12 +548,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
throw new InvoiceApiException(ErrorCode.ACCOUNT_DOES_NOT_HAVE_PARENT_ACCOUNT, childAccountId);
}
- final BigDecimal accountCBA = getAccountCBA(childAccountId, callContext);
+ final BigDecimal accountCBA = getAccountCBA(childAccountId, context);
if (accountCBA.compareTo(BigDecimal.ZERO) <= 0) {
throw new InvoiceApiException(ErrorCode.CHILD_ACCOUNT_MISSING_CREDIT, childAccountId);
}
- dao.transferChildCreditToParent(childAccount, callContext);
+ dao.transferChildCreditToParent(childAccount, internalCallContext);
}
}
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 11863c3..911c311 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
@@ -57,7 +57,6 @@ import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.cache.Cachable.CacheType;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
-import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.billing.util.dao.NonEntityDao;
@@ -1106,21 +1105,23 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
@Override
- public void transferChildCreditToParent(final Account childAccount, final CallContext context) throws InvoiceApiException {
+ public void transferChildCreditToParent(final Account childAccount, final InternalCallContext childAccountContext) throws InvoiceApiException {
transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final InternalCallContext childInternalCallContext = internalCallContextFactory.createInternalCallContext(childAccount.getId(), context);
- final InternalCallContext parentInternalCallContext = internalCallContextFactory.createInternalCallContext(childAccount.getParentAccountId(), context);
+ // Need to create an internalCallContext for parent account because it's needed to save the correct accountRecordId in Invoice tables.
+ // Then it's used to load invoices by account.
+ final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(childAccount.getParentAccountId(), childAccountContext);
+ final InternalCallContext parentAccountContext = internalCallContextFactory.createInternalCallContext(internalTenantContext.getAccountRecordId(), childAccountContext);
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final InvoiceItemSqlDao transInvoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
// create child and parent invoices
- final DateTime effectiveDate = context.getCreatedDate();
- final BigDecimal accountCBA = getAccountCBA(childAccount.getId(), childInternalCallContext);
+ final DateTime effectiveDate = childAccountContext.getCreatedDate();
+ final BigDecimal accountCBA = getAccountCBA(childAccount.getId(), childAccountContext);
// create external charge to child account
final Invoice invoiceForExternalCharge = new DefaultInvoice(childAccount.getId(), effectiveDate.toLocalDate(),
@@ -1140,7 +1141,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// create credit to parent account
final Invoice invoiceForCredit = new DefaultInvoice(childAccount.getParentAccountId(), effectiveDate.toLocalDate(), effectiveDate.toLocalDate(),
- childAccount.getCurrency(), InvoiceStatus.DRAFT);
+ childAccount.getCurrency(), InvoiceStatus.COMMITTED);
final String creditDescription = "Credit migrated from child account " + childAccount.getId();
final InvoiceItem creditItem = new CreditAdjInvoiceItem(UUIDs.randomUUID(),
effectiveDate,
@@ -1156,18 +1157,19 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
// save invoices and invoice items
InvoiceModelDao childInvoice = new InvoiceModelDao(invoiceForExternalCharge);
- invoiceSqlDao.create(childInvoice, childInternalCallContext);
- createInvoiceItemFromTransaction(transInvoiceItemSqlDao, new InvoiceItemModelDao(externalChargeItem), childInternalCallContext);
+ invoiceSqlDao.create(childInvoice, childAccountContext);
+ createInvoiceItemFromTransaction(transInvoiceItemSqlDao, new InvoiceItemModelDao(externalChargeItem), childAccountContext);
InvoiceModelDao parentInvoice = new InvoiceModelDao(invoiceForCredit);
- invoiceSqlDao.create(parentInvoice, parentInternalCallContext);
- createInvoiceItemFromTransaction(transInvoiceItemSqlDao, new InvoiceItemModelDao(creditItem), parentInternalCallContext);
+ invoiceSqlDao.create(parentInvoice, parentAccountContext);
+ createInvoiceItemFromTransaction(transInvoiceItemSqlDao, new InvoiceItemModelDao(creditItem), parentAccountContext);
// add CBA complexity and notify bus on child invoice creation
- cbaDao.addCBAComplexityFromTransaction(childInvoice.getId(), entitySqlDaoWrapperFactory, childInternalCallContext);
- notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, childInvoice, childInternalCallContext);
+ cbaDao.addCBAComplexityFromTransaction(childInvoice.getId(), entitySqlDaoWrapperFactory, childAccountContext);
+ notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, childInvoice, childAccountContext);
- cbaDao.addCBAComplexityFromTransaction(parentInvoice.getId(), entitySqlDaoWrapperFactory, parentInternalCallContext);
+ cbaDao.addCBAComplexityFromTransaction(parentInvoice.getId(), entitySqlDaoWrapperFactory, parentAccountContext);
+ notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, parentInvoice, parentAccountContext);
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 216e47d..d1d16f1 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
@@ -34,7 +34,6 @@ import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceStatus;
-import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.EntityDao;
@@ -200,8 +199,8 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
* Move a given child credit to the parent level
*
* @param childAccount the child account
- * @param context the tenant context
+ * @param childAccountContext the tenant context for the child account id
* @throws InvoiceApiException if any unexpected error occurs
*/
- void transferChildCreditToParent(Account childAccount, CallContext context) throws InvoiceApiException;
+ void transferChildCreditToParent(Account childAccount, InternalCallContext childAccountContext) throws InvoiceApiException;
}
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 6a5da4d..2aeba34 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -737,8 +737,7 @@ public class InvoiceDispatcher {
invoices.add(draftParentInvoice);
invoiceDao.createInvoices(invoices, parentContext);
} else {
- // do nothing if child invoice has negative amount because it's a credit and it will be use in next invoice
- if (childInvoiceAmount.compareTo(BigDecimal.ZERO) < 0) return;
+ if (shouldIgnoreChildInvoice(childInvoice, childInvoiceAmount)) return;
draftParentInvoice = new InvoiceModelDao(account.getParentAccountId(), today.toLocalDate(), account.getCurrency(), InvoiceStatus.DRAFT, true);
InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, draftParentInvoice.getId(), account.getParentAccountId(), account.getId(), childInvoiceAmount, account.getCurrency(), description);
@@ -756,6 +755,25 @@ public class InvoiceDispatcher {
}
+ private boolean shouldIgnoreChildInvoice(final Invoice childInvoice, final BigDecimal childInvoiceAmount) {
+
+ switch (childInvoiceAmount.compareTo(BigDecimal.ZERO)) {
+ case -1 : {
+ // do nothing if child invoice has negative amount because it's a credit and it will be use in next invoice
+ return true;
+ }
+ case 1 : return false;
+ case 0 : {
+ // only ignore if amount == 0 and any item is not FIXED or RECURRING
+ for (InvoiceItem item : childInvoice.getInvoiceItems()) {
+ if (item.getInvoiceItemType().equals(InvoiceItemType.FIXED) || item.getInvoiceItemType().equals(InvoiceItemType.RECURRING)) return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
public void processParentInvoiceForAdjustments(final ImmutableAccountData account, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
final InvoiceModelDao childInvoiceModelDao = invoiceDao.getById(childInvoiceId, context);
@@ -775,7 +793,8 @@ public class InvoiceDispatcher {
return input.getType().equals(InvoiceItemType.ITEM_ADJ) || input.getType().equals(InvoiceItemType.REPAIR_ADJ);
}
});
- // TODO should I add a NPE check?
+ if (childInvoiceItemAdjustment == null) return;
+
final BigDecimal childInvoiceAdjustmentAmount = childInvoiceItemAdjustment.getAmount();
final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(account.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
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 7844d52..9148a33 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
@@ -37,7 +37,6 @@ import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
-import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Pagination;
import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
@@ -403,7 +402,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
@Override
- public void transferChildCreditToParent(final Account childAccount, final CallContext context) throws InvoiceApiException {
+ public void transferChildCreditToParent(final Account childAccount, final InternalCallContext context) throws InvoiceApiException {
throw new UnsupportedOperationException();
}
}