killbill-memoizeit

invoice: overall HA improvements * Take the (parent) account

2/23/2017 10:44:46 AM

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