Details
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index 139ac52..27168cf 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -47,6 +48,7 @@ import org.killbill.billing.util.tag.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -103,7 +105,6 @@ public class InvoiceDaoHelper {
return outputItemIdsWithAmounts;
}
-
private static void computeItemAdjustmentsForTargetInvoiceItem(final InvoiceItemModelDao targetInvoiceItem, final List<InvoiceItemModelDao> adjustedOrRepairedItems, final Map<UUID, BigDecimal> inputAdjInvoiceItem, final Map<UUID, BigDecimal> outputAdjInvoiceItem) throws InvoiceApiException {
final BigDecimal originalItemAmount = targetInvoiceItem.getAmount();
final BigDecimal maxAdjLeftAmount = computeItemAdjustmentAmount(originalItemAmount, adjustedOrRepairedItems);
@@ -121,7 +122,7 @@ public class InvoiceDaoHelper {
/**
* @param requestedPositiveAmountToAdjust amount we are adjusting for that item
- * @param adjustedOrRepairedItems list of all adjusted or repaired linking to this item
+ * @param adjustedOrRepairedItems list of all adjusted or repaired linking to this item
* @return the amount we should really adjust based on whether or not the item got repaired
*/
private static BigDecimal computeItemAdjustmentAmount(final BigDecimal requestedPositiveAmountToAdjust, final List<InvoiceItemModelDao> adjustedOrRepairedItems) {
@@ -224,17 +225,30 @@ public class InvoiceDaoHelper {
}
public void populateChildren(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
- getInvoiceItemsWithinTransaction(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
- getInvoicePaymentsWithinTransaction(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
- setInvoiceWrittenOff(invoice, context);
- getParentInvoice(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
+ populateChildren(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
}
public void populateChildren(final Iterable<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+ if (Iterables.<InvoiceModelDao>isEmpty(invoices)) {
+ return;
+ }
+
getInvoiceItemsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
getInvoicePaymentsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
setInvoicesWrittenOff(invoices, context);
- getParentInvoice(invoices, entitySqlDaoWrapperFactory, context);
+
+ final Iterable<InvoiceModelDao> nonParentInvoices = Iterables.<InvoiceModelDao>filter(invoices,
+ new Predicate<InvoiceModelDao>() {
+ @Override
+ public boolean apply(final InvoiceModelDao invoice) {
+ return !invoice.isParentInvoice();
+ }
+ });
+ if (!Iterables.<InvoiceModelDao>isEmpty(nonParentInvoices)) {
+ setParentInvoice(nonParentInvoices,
+ entitySqlDaoWrapperFactory,
+ context);
+ }
}
public List<InvoiceModelDao> getAllInvoicesByAccountFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
@@ -270,7 +284,7 @@ public class InvoiceDaoHelper {
private void getInvoicePaymentsWithinTransaction(final Iterable<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
final InvoicePaymentSqlDao invoicePaymentSqlDao = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
- final List<InvoicePaymentModelDao> invoicePaymentsForAccount = invoicePaymentSqlDao.getByAccountRecordId(context);;
+ final List<InvoicePaymentModelDao> invoicePaymentsForAccount = invoicePaymentSqlDao.getByAccountRecordId(context);
final Map<UUID, List<InvoicePaymentModelDao>> invoicePaymentsPerInvoiceId = new HashMap<UUID, List<InvoicePaymentModelDao>>();
for (final InvoicePaymentModelDao invoicePayment : invoicePaymentsForAccount) {
@@ -297,7 +311,6 @@ public class InvoiceDaoHelper {
}
private void setInvoicesWrittenOff(final Iterable<InvoiceModelDao> invoices, final InternalTenantContext internalTenantContext) {
-
final List<Tag> tags = tagInternalApi.getTagsForAccountType(ObjectType.INVOICE, false, internalTenantContext);
final Iterable<Tag> writtenOffTags = filterForWrittenOff(tags);
for (final Tag cur : writtenOffTags) {
@@ -313,34 +326,71 @@ public class InvoiceDaoHelper {
}
}
- private void setInvoiceWrittenOff(final InvoiceModelDao invoice, final InternalTenantContext internalTenantContext) {
- final List<Tag> tags = tagInternalApi.getTags(invoice.getId(), ObjectType.INVOICE, internalTenantContext);
- final Iterable<Tag> writtenOffTags = filterForWrittenOff(tags);
- invoice.setIsWrittenOff(writtenOffTags.iterator().hasNext());
- }
-
private Iterable<Tag> filterForWrittenOff(final List<Tag> tags) {
return Iterables.filter(tags, new Predicate<Tag>() {
@Override
public boolean apply(final Tag input) {
- return input.getTagDefinitionId().equals(ControlTagType.WRITTEN_OFF.getId());
+ return input.getTagDefinitionId().equals(ControlTagType.WRITTEN_OFF.getId());
}
});
}
- private void getParentInvoice(final Iterable<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext internalTenantContext) {
+ private void setParentInvoice(final Iterable<InvoiceModelDao> childInvoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext childContext) {
+ final Collection<String> childInvoiceIds = new HashSet<String>();
+ for (final InvoiceModelDao childInvoice : childInvoices) {
+ childInvoiceIds.add(childInvoice.getId().toString());
+ }
+
+ // DAO: retrieve the mappings between parent and child invoices
+ final InvoiceParentChildrenSqlDao invoiceParentChildrenSqlDao = entitySqlDaoWrapperFactory.become(InvoiceParentChildrenSqlDao.class);
+ final List<InvoiceParentChildModelDao> mappings = invoiceParentChildrenSqlDao.getParentChildMappingsByChildInvoiceIds(childInvoiceIds, childContext);
+ if (mappings.isEmpty()) {
+ return;
+ }
+ final Map<UUID, InvoiceParentChildModelDao> mappingPerChildInvoiceId = new HashMap<UUID, InvoiceParentChildModelDao>();
+ final Collection<String> parentInvoiceIdsAsStrings = new HashSet<String>();
+ for (final InvoiceParentChildModelDao mapping : mappings) {
+ mappingPerChildInvoiceId.put(mapping.getChildInvoiceId(), mapping);
+ parentInvoiceIdsAsStrings.add(mapping.getParentInvoiceId().toString());
+ }
+
+ // DAO: retrieve all parents invoices in bulk, for all child invoices
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
- for (InvoiceModelDao invoice : invoices) {
- if (invoice.isParentInvoice()) continue;
- final InvoiceModelDao parentInvoice = invoiceSqlDao.getParentInvoiceByChildInvoiceId(invoice.getId().toString(), internalTenantContext);
+ final List<InvoiceModelDao> parentInvoices = invoiceSqlDao.getByIds(parentInvoiceIdsAsStrings, childContext);
+
+ // Group the parent invoices by (parent) account id (most likely, we only have one parent account group, except in re-parenting cases)
+ final Map<UUID, List<InvoiceModelDao>> parentInvoicesGroupedByParentAccountId = new HashMap<UUID, List<InvoiceModelDao>>();
+ // Create also a convenient mapping (needed below later)
+ final Map<UUID, InvoiceModelDao> parentInvoiceByParentInvoiceId = new HashMap<UUID, InvoiceModelDao>();
+ for (final InvoiceModelDao parentInvoice : parentInvoices) {
+ if (parentInvoicesGroupedByParentAccountId.get(parentInvoice.getAccountId()) == null) {
+ parentInvoicesGroupedByParentAccountId.put(parentInvoice.getAccountId(), new LinkedList<InvoiceModelDao>());
+ }
+ parentInvoicesGroupedByParentAccountId.get(parentInvoice.getAccountId()).add(parentInvoice);
+
+ parentInvoiceByParentInvoiceId.put(parentInvoice.getId(), parentInvoice);
+ }
+
+ // DAO: populate the parent invoices in bulk
+ for (final UUID parentAccountId : parentInvoicesGroupedByParentAccountId.keySet()) {
+ final List<InvoiceModelDao> parentInvoicesForOneParentAccountId = parentInvoicesGroupedByParentAccountId.get(parentAccountId);
+ final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(parentAccountId, ObjectType.ACCOUNT, internalCallContextFactory.createTenantContext(childContext));
+ final InternalTenantContext parentContext = internalCallContextFactory.createInternalTenantContext(childContext.getTenantRecordId(), parentAccountRecordId);
+ // Note the misnomer here, populateChildren simply populates the content of these invoices (unrelated to HA)
+ populateChildren(parentInvoicesForOneParentAccountId, entitySqlDaoWrapperFactory, parentContext);
+ }
+
+ for (final InvoiceModelDao invoice : childInvoices) {
+ final InvoiceParentChildModelDao mapping = mappingPerChildInvoiceId.get(invoice.getId());
+ if (mapping == null) {
+ continue;
+ }
+
+ final InvoiceModelDao parentInvoice = parentInvoiceByParentInvoiceId.get(mapping.getParentInvoiceId());
if (parentInvoice != null) {
- final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(parentInvoice.getAccountId(), ObjectType.ACCOUNT, internalCallContextFactory.createTenantContext(internalTenantContext));
- final InternalTenantContext parentContext = internalCallContextFactory.createInternalTenantContext(internalTenantContext.getTenantRecordId(), parentAccountRecordId);
- populateChildren(parentInvoice, entitySqlDaoWrapperFactory, parentContext);
invoice.addParentInvoice(parentInvoice);
}
}
}
-
}
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 8043b07..b3f5602 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
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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
@@ -191,11 +191,12 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
this.status = status;
}
+ @SuppressWarnings("unused")
public void setParentInvoice(final boolean isParentInvoice) {
this.isParentInvoice = isParentInvoice;
}
- public void addParentInvoice(InvoiceModelDao parentInvoice) {
+ public void addParentInvoice(final InvoiceModelDao parentInvoice) {
this.parentInvoice = parentInvoice;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java
index 2e82ba8..2973f53 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.java
@@ -17,12 +17,15 @@
package org.killbill.billing.invoice.dao;
+import java.util.Collection;
import java.util.List;
+import java.util.UUID;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.invoice.api.InvoiceParentChild;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -34,5 +37,9 @@ public interface InvoiceParentChildrenSqlDao extends EntitySqlDao<InvoiceParentC
List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(@Bind("parentInvoiceId") final String parentInvoiceId,
@BindBean final InternalTenantContext context);
+ @SqlQuery
+ List<InvoiceParentChildModelDao> getParentChildMappingsByChildInvoiceIds(@UUIDCollectionBinder final Collection<String> childInvoiceIds,
+ @BindBean final InternalTenantContext context);
+
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java
index f4a4b98..9ed8f28 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceSqlDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project 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:
*
@@ -16,6 +18,7 @@
package org.killbill.billing.invoice.dao;
+import java.util.Collection;
import java.util.List;
import java.util.UUID;
@@ -26,6 +29,7 @@ import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -45,15 +49,15 @@ public interface InvoiceSqlDao extends EntitySqlDao<InvoiceModelDao, Invoice> {
@SqlUpdate
@Audited(ChangeType.UPDATE)
void updateStatus(@Bind("id") String invoiceId,
- @Bind("status") String status,
- @BindBean final InternalCallContext context);
+ @Bind("status") String status,
+ @BindBean final InternalCallContext context);
@SqlQuery
InvoiceModelDao getParentDraftInvoice(@Bind("accountId") final String parentAccountId,
- @BindBean final InternalTenantContext context);
+ @BindBean final InternalTenantContext context);
@SqlQuery
- InvoiceModelDao getParentInvoiceByChildInvoiceId(@Bind("childInvoiceId") final String childInvoiceId,
- @BindBean final InternalTenantContext context);
+ List<InvoiceModelDao> getByIds(@UUIDCollectionBinder final Collection<String> invoiceIds,
+ @BindBean final InternalTenantContext context);
}
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 2d28f08..590f3f1 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -771,35 +771,52 @@ public class InvoiceDispatcher {
}
}
- public void processParentInvoiceForInvoiceGeneration(final Account account, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+ public void processParentInvoiceForInvoiceGeneration(final Account childAccount, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+ GlobalLock lock = null;
+ try {
+ lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), childAccount.getParentAccountId().toString(), invoiceConfig.getMaxGlobalLockRetries());
+
+ processParentInvoiceForInvoiceGenerationWithLock(childAccount, childInvoiceId, context);
+ } catch (final LockFailedException e) {
+ log.warn("Failed to process parent invoice for parentAccountId='{}'", childAccount.getParentAccountId().toString(), e);
+ } finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ }
+ private void processParentInvoiceForInvoiceGenerationWithLock(final Account childAccount, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+ log.info("Processing parent invoice for parentAccountId='{}', childInvoiceId='{}'", childAccount.getParentAccountId(), childInvoiceId);
final InvoiceModelDao childInvoiceModelDao = invoiceDao.getById(childInvoiceId, context);
final Invoice childInvoice = new DefaultInvoice(childInvoiceModelDao);
- final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(account.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
+ final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(childAccount.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
final InternalCallContext parentContext = internalCallContextFactory.createInternalCallContext(parentAccountRecordId, context);
BigDecimal childInvoiceAmount = InvoiceCalculatorUtils.computeChildInvoiceAmount(childInvoice.getCurrency(), childInvoice.getInvoiceItems());
- InvoiceModelDao draftParentInvoice = invoiceDao.getParentDraftInvoice(account.getParentAccountId(), parentContext);
+ InvoiceModelDao draftParentInvoice = invoiceDao.getParentDraftInvoice(childAccount.getParentAccountId(), parentContext);
- final String description = account.getExternalKey().concat(" summary");
+ final String description = childAccount.getExternalKey().concat(" summary");
if (draftParentInvoice != null) {
-
for (InvoiceItemModelDao item : draftParentInvoice.getInvoiceItems()) {
if ((item.getChildAccountId() != null) && item.getChildAccountId().equals(childInvoice.getAccountId())) {
// update child item amount for existing parent invoice item
BigDecimal newChildInvoiceAmount = childInvoiceAmount.add(item.getAmount());
+ log.info("Updating existing itemId='{}', oldAmount='{}', newAmount='{}' on existing DRAFT invoiceId='{}'", item.getId(), item.getAmount(), newChildInvoiceAmount, draftParentInvoice.getId());
invoiceDao.updateInvoiceItemAmount(item.getId(), newChildInvoiceAmount, parentContext);
return;
}
}
// new item when the parent invoices does not have this child item yet
- final ParentInvoiceItem newParentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), context.getCreatedDate(), draftParentInvoice.getId(), account.getParentAccountId(), account.getId(), childInvoiceAmount, account.getCurrency(), description);
- draftParentInvoice.addInvoiceItem(new InvoiceItemModelDao(newParentInvoiceItem));
+ final ParentInvoiceItem newParentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), context.getCreatedDate(), draftParentInvoice.getId(), childAccount.getParentAccountId(), childAccount.getId(), childInvoiceAmount, childAccount.getCurrency(), description);
+ final InvoiceItemModelDao parentInvoiceItem = new InvoiceItemModelDao(newParentInvoiceItem);
+ draftParentInvoice.addInvoiceItem(parentInvoiceItem);
List<InvoiceModelDao> invoices = new ArrayList<InvoiceModelDao>();
invoices.add(draftParentInvoice);
+ log.info("Adding new itemId='{}', amount='{}' on existing DRAFT invoiceId='{}'", parentInvoiceItem.getId(), childInvoiceAmount, draftParentInvoice.getId());
invoiceDao.createInvoices(invoices, parentContext);
} else {
if (shouldIgnoreChildInvoice(childInvoice, childInvoiceAmount)) {
@@ -807,20 +824,20 @@ public class InvoiceDispatcher {
}
final LocalDate invoiceDate = context.toLocalDate(context.getCreatedDate());
- draftParentInvoice = new InvoiceModelDao(account.getParentAccountId(), invoiceDate, account.getCurrency(), InvoiceStatus.DRAFT, true);
- final InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), context.getCreatedDate(), draftParentInvoice.getId(), account.getParentAccountId(), account.getId(), childInvoiceAmount, account.getCurrency(), description);
+ draftParentInvoice = new InvoiceModelDao(childAccount.getParentAccountId(), invoiceDate, childAccount.getCurrency(), InvoiceStatus.DRAFT, true);
+ final InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), context.getCreatedDate(), draftParentInvoice.getId(), childAccount.getParentAccountId(), childAccount.getId(), childInvoiceAmount, childAccount.getCurrency(), description);
draftParentInvoice.addInvoiceItem(new InvoiceItemModelDao(parentInvoiceItem));
// build account date time zone
final FutureAccountNotifications futureAccountNotifications = new FutureAccountNotifications(ImmutableMap.<UUID, List<SubscriptionNotification>>of());
+ log.info("Adding new itemId='{}', amount='{}' on new DRAFT invoiceId='{}'", parentInvoiceItem.getId(), childInvoiceAmount, draftParentInvoice.getId());
invoiceDao.createInvoice(draftParentInvoice, draftParentInvoice.getInvoiceItems(), true, futureAccountNotifications, parentContext);
}
// save parent child invoice relation
- final InvoiceParentChildModelDao invoiceRelation = new InvoiceParentChildModelDao(draftParentInvoice.getId(), childInvoiceId, account.getId());
+ final InvoiceParentChildModelDao invoiceRelation = new InvoiceParentChildModelDao(draftParentInvoice.getId(), childInvoiceId, childAccount.getId());
invoiceDao.createParentChildInvoiceRelation(invoiceRelation, parentContext);
-
}
private boolean shouldIgnoreChildInvoice(final Invoice childInvoice, final BigDecimal childInvoiceAmount) {
@@ -840,8 +857,22 @@ public class InvoiceDispatcher {
return true;
}
- public void processParentInvoiceForAdjustments(final AccountData account, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+ public void processParentInvoiceForAdjustments(final Account childAccount, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+ GlobalLock lock = null;
+ try {
+ lock = locker.lockWithNumberOfTries(LockerType.ACCNT_INV_PAY.toString(), childAccount.getParentAccountId().toString(), invoiceConfig.getMaxGlobalLockRetries());
+
+ processParentInvoiceForAdjustmentsWithLock(childAccount, childInvoiceId, context);
+ } catch (final LockFailedException e) {
+ log.warn("Failed to process parent invoice for parentAccountId='{}'", childAccount.getParentAccountId().toString(), e);
+ } finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ }
+ public void processParentInvoiceForAdjustmentsWithLock(final Account account, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {
final InvoiceModelDao childInvoiceModelDao = invoiceDao.getById(childInvoiceId, context);
final InvoiceModelDao parentInvoiceModelDao = childInvoiceModelDao.getParentInvoice();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
index 49824f4..155e941 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
@@ -83,7 +83,6 @@ public class InvoiceListener {
}
}
-
@AllowConcurrentEvents
@Subscribe
public void handleBlockingStateTransition(final BlockingTransitionInternalEvent event) {
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg
index e4f9726..2a96b7a 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceParentChildrenSqlDao.sql.stg
@@ -30,8 +30,16 @@ allTableValues() ::= <<
getChildInvoicesByParentInvoiceId() ::= <<
SELECT <allTableFields()>
- FROM <tableName()>
- WHERE parent_invoice_id = :parentInvoiceId
- <AND_CHECK_TENANT()>
- <defaultOrderBy()>
+ FROM <tableName()>
+ WHERE parent_invoice_id = :parentInvoiceId
+ <AND_CHECK_TENANT()>
+ <defaultOrderBy()>
+ >>
+
+getParentChildMappingsByChildInvoiceIds(ids) ::= <<
+ SELECT <allTableFields()>
+ FROM <tableName()>
+ WHERE child_invoice_id in (<ids: {id | :id_<i0>}; separator="," >)
+ <AND_CHECK_TENANT()>
+ <defaultOrderBy()>
>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 02de5dc..812767f 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -71,11 +71,11 @@ getParentDraftInvoice() ::= <<
<defaultOrderBy()>
>>
-getParentInvoiceByChildInvoiceId() ::= <<
- SELECT <allTableFields("i.")>
- FROM <tableName()> i
- INNER JOIN invoice_parent_children ipc ON i.id = ipc.parent_invoice_id
- WHERE ipc.child_invoice_id = :childInvoiceId
- <AND_CHECK_TENANT("i.")>
- <AND_CHECK_TENANT("ipc.")>
- >>
\ No newline at end of file
+getByIds(ids) ::= <<
+select
+ <allTableFields("t.")>
+from <tableName()> t
+where <idField("t.")> in (<ids: {id | :id_<i0>}; separator="," >)
+<AND_CHECK_TENANT("t.")>
+;
+>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
index 606f8bc..52f4ee7 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -81,8 +81,6 @@ CREATE INDEX invoice_payments_payment_id ON invoice_payments(payment_id);
CREATE INDEX invoice_payments_payment_cookie_id ON invoice_payments(payment_cookie_id);
CREATE INDEX invoice_payments_tenant_account_record_id ON invoice_payments(tenant_record_id, account_record_id);
-
-
DROP TABLE IF EXISTS invoice_parent_children;
CREATE TABLE invoice_parent_children (
record_id serial unique,
@@ -99,3 +97,4 @@ CREATE TABLE invoice_parent_children (
CREATE UNIQUE INDEX invoice_parent_children_id ON invoice_parent_children(id);
CREATE INDEX invoice_parent_children_invoice_id ON invoice_parent_children(parent_invoice_id);
CREATE INDEX invoice_parent_children_tenant_account_record_id ON invoice_parent_children(tenant_record_id, account_record_id);
+CREATE INDEX invoice_parent_children_child_invoice_id ON invoice_parent_children(child_invoice_id);
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20170222063630__invoice_parent_children_child_invoice_id_idx.sql b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20170222063630__invoice_parent_children_child_invoice_id_idx.sql
new file mode 100644
index 0000000..5b75e31
--- /dev/null
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20170222063630__invoice_parent_children_child_invoice_id_idx.sql
@@ -0,0 +1 @@
+alter table invoice_parent_children add index invoice_parent_children_child_invoice_id(child_invoice_id);