killbill-memoizeit

#459 : new NotificationQ for ParentInvoice commit accion and

2/12/2016 6:25:59 PM

Changes

Details

diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
index b50879e..7c930cd 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
@@ -75,4 +75,6 @@ public interface InvoiceInternalApi {
     public void consumeExistingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context) throws InvoiceApiException;
 
     public Map<UUID, BigDecimal> validateInvoiceItemAdjustments(final UUID paymentId, final Map<UUID, BigDecimal> idWithAmount, final InternalTenantContext context) throws InvoiceApiException;
+
+    public void commitInvoice(UUID invoiceId, InternalCallContext context) throws InvoiceApiException;
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index 9bf78b2..678f286 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -366,26 +366,23 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         assertEquals(parentInvoice.getNumberOfItems(), 2);
         assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
         assertTrue(parentInvoice.isParentInvoice());
-        //assertEquals(parentInvoice.getBalance(), BigDecimal.ZERO);
+        assertEquals(parentInvoice.getBalance().toString(), "0.00");
 
-        // No payment is expected because balance is 0
+        // Moving a day the NotificationQ calls the commitInvoice. No payment is expected because balance is 0
         busHandler.pushExpectedEvents(NextEvent.INVOICE);
-        invoiceUserApi.commitInvoice(parentInvoice.getId(), callContext);
+        clock.addDays(1);
         assertListenerStatus();
 
         parentInvoice = invoiceUserApi.getInvoice(parentInvoice.getId(), callContext);
         assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
 
-        // Move through time and verify new parent Invoice
+        // Move through time and verify new parent Invoice. No payments are expected.
         busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE,
-                                      NextEvent.INVOICE, NextEvent.INVOICE,
-                                      NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT,
-                                      NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT); // TODO fix when refactor invoice.getBalance
+                                      NextEvent.INVOICE, NextEvent.INVOICE);
         clock.addDays(31);
         assertListenerStatus();
 
         // Second Parent invoice over Recurring period
-
         parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), callContext);
         assertEquals(parentInvoices.size(), 2);
 
@@ -393,10 +390,11 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         assertEquals(parentInvoice.getNumberOfItems(), 2);
         assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
         assertTrue(parentInvoice.isParentInvoice());
+        assertEquals(parentInvoice.getBalance().toString(), "279.90");
 
-        // now payment is expected
-        busHandler.pushExpectedEvents(NextEvent.INVOICE); // TODO NextEvent.PAYMENT
-        invoiceUserApi.commitInvoice(parentInvoice.getId(), callContext);
+        // Moving a day the NotificationQ calls the commitInvoice. Now payment is expected
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(1);
         assertListenerStatus();
 
         parentInvoice = invoiceUserApi.getInvoice(parentInvoice.getId(), callContext);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
index 961d3e7..82dab56 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
@@ -18,6 +18,7 @@
 
 package org.killbill.billing.invoice.api;
 
+import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentNotifier;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.invoice.InvoiceListener;
 import org.killbill.billing.invoice.InvoiceTagHandler;
@@ -36,13 +37,16 @@ public class DefaultInvoiceService implements InvoiceService {
     private final InvoiceListener invoiceListener;
     private final InvoiceTagHandler tagHandler;
     private final PersistentBus eventBus;
+    private final ParentInvoiceCommitmentNotifier parentInvoiceNotifier;
 
     @Inject
-    public DefaultInvoiceService(final InvoiceListener invoiceListener, final InvoiceTagHandler tagHandler, final PersistentBus eventBus, final NextBillingDateNotifier dateNotifier) {
+    public DefaultInvoiceService(final InvoiceListener invoiceListener, final InvoiceTagHandler tagHandler, final PersistentBus eventBus,
+                                 final NextBillingDateNotifier dateNotifier, final ParentInvoiceCommitmentNotifier parentInvoiceNotifier) {
         this.invoiceListener = invoiceListener;
         this.tagHandler = tagHandler;
         this.eventBus = eventBus;
         this.dateNotifier = dateNotifier;
+        this.parentInvoiceNotifier = parentInvoiceNotifier;
     }
 
     @Override
@@ -59,11 +63,13 @@ public class DefaultInvoiceService implements InvoiceService {
             throw new RuntimeException("Unable to register to the EventBus!", e);
         }
         dateNotifier.initialize();
+        parentInvoiceNotifier.initialize();
     }
 
     @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
     public void start() {
         dateNotifier.start();
+        parentInvoiceNotifier.start();
     }
 
     @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
@@ -75,5 +81,6 @@ public class DefaultInvoiceService implements InvoiceService {
             throw new RuntimeException("Unable to unregister to the EventBus!", e);
         }
         dateNotifier.stop();
+        parentInvoiceNotifier.stop();
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index 80472a9..fcdd838 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -40,6 +40,7 @@ import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.invoice.api.InvoiceStatus;
 import org.killbill.billing.invoice.api.WithAccountLock;
 import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
@@ -182,4 +183,9 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
             }
         }).orNull();
     }
+
+    @Override
+    public void commitInvoice(final UUID invoiceId, final InternalCallContext context) throws InvoiceApiException {
+        dao.changeInvoiceStatus(invoiceId, InvoiceStatus.COMMITTED, context);
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
index eceab78..23b11ef 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
@@ -56,6 +56,11 @@ public abstract class InvoiceCalculatorUtils {
         return InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType());
     }
 
+    // Item from Parent Invoice
+    public static boolean isParentSummaryItem(final InvoiceItem invoiceItem) {
+        return InvoiceItemType.PARENT_SUMMARY.equals(invoiceItem.getInvoiceItemType());
+    }
+
     // Regular line item (charges)
     public static boolean isCharge(final InvoiceItem invoiceItem) {
         return InvoiceItemType.TAX.equals(invoiceItem.getInvoiceItemType()) ||
@@ -124,7 +129,8 @@ public abstract class InvoiceCalculatorUtils {
 
             if (isCharge(invoiceItem) ||
                 isInvoiceAdjustmentItem(invoiceItem, otherInvoiceItems) ||
-                isInvoiceItemAdjustmentItem(invoiceItem)) {
+                isInvoiceItemAdjustmentItem(invoiceItem) ||
+                isParentSummaryItem(invoiceItem)) {
                 amountCharged = amountCharged.add(invoiceItem.getAmount());
             }
         }
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 04ab792..c91a235 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
@@ -29,6 +29,7 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
@@ -48,6 +49,8 @@ import org.killbill.billing.invoice.api.InvoiceStatus;
 import org.killbill.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent;
 import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
 import org.killbill.billing.invoice.notification.NextBillingDatePoster;
+import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
+import org.killbill.billing.util.AccountDateAndTimeZoneContext;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
@@ -60,6 +63,7 @@ import org.killbill.billing.util.entity.dao.EntityDaoBase;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.billing.util.timezone.DefaultAccountDateAndTimeZoneContext;
 import org.killbill.bus.api.BusEvent;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
@@ -104,6 +108,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     private final Clock clock;
     private final CacheControllerDispatcher cacheControllerDispatcher;
     private final NonEntityDao nonEntityDao;
+    private final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster;
 
     @Inject
     public DefaultInvoiceDao(final IDBI dbi,
@@ -115,6 +120,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                              final InvoiceConfig invoiceConfig,
                              final InvoiceDaoHelper invoiceDaoHelper,
                              final CBADao cbaDao,
+                             final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster,
                              final InternalCallContextFactory internalCallContextFactory) {
         super(new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao), InvoiceSqlDao.class);
         this.nextBillingDatePoster = nextBillingDatePoster;
@@ -126,6 +132,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         this.clock = clock;
         this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.nonEntityDao = nonEntityDao;
+        this.parentInvoiceCommitmentPoster = parentInvoiceCommitmentPoster;
     }
 
     @Override
@@ -271,6 +278,9 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     if (InvoiceStatus.COMMITTED.equals(invoice.getStatus())) {
                         notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoice.getAccountId(), callbackDateTimePerSubscriptions, context);
                     }
+                    if (invoice.isParentInvoice()) {
+                        notifyOfParentInvoiceCreation(entitySqlDaoWrapperFactory, invoice, callbackDateTimePerSubscriptions, context);
+                    }
                 }
                 return null;
             }
@@ -948,7 +958,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 if (InvoiceStatus.COMMITTED.equals(newStatus)) {
                     // notify invoice creation event
                     notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoice, context);
-                    // notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoice.getAccountId(), callbackDateTimePerSubscriptions, context);
                 }
 
                 return null;
@@ -969,6 +978,18 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         }
     }
 
+    private void notifyOfParentInvoiceCreation(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InvoiceModelDao parentInvoice,
+                                               final FutureAccountNotifications callbackDateTime, final InternalCallContext context) {
+        DateTime futureNotificationDate = parentInvoice.getCreatedDate();
+        AccountDateAndTimeZoneContext accountDateAndTimeZone;
+        if ((callbackDateTime != null) && (callbackDateTime.getAccountDateAndTimeZoneContext() != null)) {
+            accountDateAndTimeZone = callbackDateTime.getAccountDateAndTimeZoneContext();
+        } else {
+            accountDateAndTimeZone = new DefaultAccountDateAndTimeZoneContext(futureNotificationDate, DateTimeZone.UTC);
+        }
+        parentInvoiceCommitmentPoster.insertParentInvoiceFromTransactionInternal(entitySqlDaoWrapperFactory, parentInvoice.getId(), futureNotificationDate, accountDateAndTimeZone, context);
+    }
+
     @Override
     public void createParentChildInvoiceRelation(final InvoiceParentChildModelDao invoiceRelation, final InternalCallContext context) throws InvoiceApiException {
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
@@ -982,6 +1003,17 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
+    public List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(final UUID parentInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceParentChildModelDao>>() {
+            @Override
+            public List<InvoiceParentChildModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                final InvoiceParentChildrenSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceParentChildrenSqlDao.class);
+                return transactional.getChildInvoicesByParentInvoiceId(parentInvoiceId.toString(), context);
+            }
+        });
+    }
+
+    @Override
     public InvoiceModelDao getParentDraftInvoice(final UUID parentAccountId, final InternalCallContext context) throws InvoiceApiException {
         return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoiceModelDao>() {
             @Override
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 4dec8ec..ab6ee1a 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
@@ -160,6 +160,16 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
     void createParentChildInvoiceRelation(final InvoiceParentChildModelDao invoiceRelation, final InternalCallContext context) throws InvoiceApiException;
 
     /**
+     * Get parent/child invoice relationship by parent invoice id
+     *
+     * @param parentInvoiceId the parent invoice id
+     * @param context the tenant context
+     * @throws InvoiceApiException
+     */
+    List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(UUID parentInvoiceId, final InternalCallContext context) throws InvoiceApiException;
+
+
+    /**
      * Retrieve parent invoice by the parent account id
      *
      * @param parentAccountId the parent account id
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 0919880..ce4da0d 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
@@ -83,7 +83,7 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
     }
 
     public InvoiceModelDao(final UUID accountId, final LocalDate invoiceDate, final Currency currency, final InvoiceStatus status, final boolean isParentInvoice) {
-        this(UUIDs.randomUUID(), null, accountId, null, invoiceDate, null, currency, false, status, isParentInvoice);
+        this(UUIDs.randomUUID(), invoiceDate.toDateTimeAtCurrentTime(), accountId, null, invoiceDate, null, currency, false, status, isParentInvoice);
     }
 
     public InvoiceModelDao(final Invoice invoice) {
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 3d62a1a..2e82ba8 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,13 +17,22 @@
 
 package org.killbill.billing.invoice.dao;
 
+import java.util.List;
+
+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.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
 
 @EntitySqlDaoStringTemplate
 public interface InvoiceParentChildrenSqlDao extends EntitySqlDao<InvoiceParentChildModelDao, InvoiceParentChild> {
 
+    @SqlQuery
+    List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(@Bind("parentInvoiceId") final String parentInvoiceId,
+                                                                       @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 d35ee7e..7aa89dc 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -94,6 +94,7 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.config.InvoiceConfig;
 import org.killbill.billing.util.globallocker.LockerType;
+import org.killbill.billing.util.timezone.DefaultAccountDateAndTimeZoneContext;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.clock.Clock;
@@ -716,17 +717,23 @@ public class InvoiceDispatcher {
         final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(account.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
         final InternalCallContext parentContext = new InternalCallContext(context, parentAccountRecordId);
 
+        final DateTime today = clock.getNow(account.getTimeZone());
         if (parentInvoice != null) {
-            InvoiceItem invoiceItem = new ParentInvoiceItem(UUID.randomUUID(), DateTime.now(), parentInvoice.getId(), account.getParentAccountId(), account.getId(), invoiceAmount, account.getCurrency());
+            InvoiceItem invoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), account.getParentAccountId(), account.getId(), invoiceAmount, account.getCurrency());
             parentInvoice.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));
             List<InvoiceModelDao> invoices = new ArrayList<InvoiceModelDao>();
             invoices.add(parentInvoice);
             invoiceDao.createInvoices(invoices, parentContext);
         } else {
-            parentInvoice = new InvoiceModelDao(account.getParentAccountId(), LocalDate.now(), account.getCurrency(), InvoiceStatus.DRAFT, true);
-            InvoiceItem invoiceItem = new ParentInvoiceItem(UUID.randomUUID(), DateTime.now(), parentInvoice.getId(), account.getParentAccountId(), account.getId(), invoiceAmount, account.getCurrency());
+            parentInvoice = new InvoiceModelDao(account.getParentAccountId(), today.toLocalDate(), account.getCurrency(), InvoiceStatus.DRAFT, true);
+            InvoiceItem invoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), account.getParentAccountId(), account.getId(), invoiceAmount, account.getCurrency());
             parentInvoice.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));
-            invoiceDao.createInvoice(parentInvoice, parentInvoice.getInvoiceItems(), true, null, parentContext);
+
+            // build account date time zone
+            final AccountDateAndTimeZoneContext accountDateTimeZone = new DefaultAccountDateAndTimeZoneContext(today, account.getTimeZone());
+            final FutureAccountNotifications futureAccountNotifications = new FutureAccountNotifications(accountDateTimeZone, null);
+
+            invoiceDao.createInvoice(parentInvoice, parentInvoice.getInvoiceItems(), true, futureAccountNotifications, parentContext);
         }
 
         // save parent child invoice relation
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 db59d84..7642e72 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
@@ -19,7 +19,6 @@ package org.killbill.billing.invoice;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.ImmutableAccountData;
@@ -171,4 +170,13 @@ public class InvoiceListener {
         return account.getParentAccountId() != null && account.isPaymentDelegatedToParent();
     }
 
+    public void handleParentInvoiceCommitmentEvent(final UUID invoiceId, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+        try {
+            final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Commit Invoice", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
+            invoiceApi.commitInvoice(invoiceId, context);
+        } catch (InvoiceApiException e) {
+            log.error(e.getMessage());
+        }
+    }
+
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentNotificationKey.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentNotificationKey.java
new file mode 100644
index 0000000..3e927f7
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentNotificationKey.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.notification;
+
+import java.util.UUID;
+
+import org.killbill.notificationq.DefaultUUIDNotificationKey;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class ParentInvoiceCommitmentNotificationKey extends DefaultUUIDNotificationKey {
+
+    @JsonCreator
+    public ParentInvoiceCommitmentNotificationKey(@JsonProperty("uuidKey") final UUID uuidKey) {
+        super(uuidKey);
+    }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentNotifier.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentNotifier.java
new file mode 100644
index 0000000..61437c2
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentNotifier.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.notification;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.invoice.InvoiceListener;
+import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.InvoiceConfig;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
+import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class ParentInvoiceCommitmentNotifier implements NextBillingDateNotifier {
+
+    private static final Logger log = LoggerFactory.getLogger(ParentInvoiceCommitmentNotifier.class);
+
+    public static final String PARENT_INVOICE_COMMITMENT_NOTIFIER_QUEUE = "parent-invoice-commitment-queue";
+
+    private final NotificationQueueService notificationQueueService;
+    private final InvoiceConfig config;
+    private final InvoiceListener listener;
+    private final InternalCallContextFactory callContextFactory;
+
+    private NotificationQueue commitInvoiceQueue;
+
+    @Inject
+    public ParentInvoiceCommitmentNotifier(final NotificationQueueService notificationQueueService,
+                                           final InvoiceConfig config,
+                                           final InvoiceListener listener,
+                                           final InternalCallContextFactory callContextFactory) {
+        this.notificationQueueService = notificationQueueService;
+        this.config = config;
+        this.listener = listener;
+        this.callContextFactory = callContextFactory;
+    }
+
+    @Override
+    public void initialize() throws NotificationQueueAlreadyExists {
+
+        final NotificationQueueHandler notificationQueueHandler = new NotificationQueueHandler() {
+            @Override
+            public void handleReadyNotification(final NotificationEvent notificationKey, final DateTime eventDate, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+                try {
+                    if (!(notificationKey instanceof ParentInvoiceCommitmentNotificationKey)) {
+                        log.error("Invoice service received an unexpected event type {}", notificationKey.getClass().getName());
+                        return;
+                    }
+
+                    final ParentInvoiceCommitmentNotificationKey key = (ParentInvoiceCommitmentNotificationKey) notificationKey;
+
+                    listener.handleParentInvoiceCommitmentEvent(key.getUuidKey(), userToken, accountRecordId, tenantRecordId);
+
+                } catch (IllegalArgumentException e) {
+                    log.error("The key returned from the ParentInvoiceCommitmentQueue is not a valid UUID", e);
+                }
+            }
+        };
+
+        commitInvoiceQueue = notificationQueueService.createNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+                                                                              PARENT_INVOICE_COMMITMENT_NOTIFIER_QUEUE,
+                                                                              notificationQueueHandler);
+    }
+
+    @Override
+    public void start() {
+        commitInvoiceQueue.startQueue();
+    }
+
+    @Override
+    public void stop() throws NoSuchNotificationQueue {
+        if (commitInvoiceQueue != null) {
+            commitInvoiceQueue.stopQueue();
+            notificationQueueService.deleteNotificationQueue(commitInvoiceQueue.getServiceName(), commitInvoiceQueue.getQueueName());
+        }
+    }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
new file mode 100644
index 0000000..58f4ca6
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.notification;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.util.AccountDateAndTimeZoneContext;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+public class ParentInvoiceCommitmentPoster {
+
+    private static final Logger log = LoggerFactory.getLogger(ParentInvoiceCommitmentPoster.class);
+
+    private final NotificationQueueService notificationQueueService;
+
+    @Inject
+    public ParentInvoiceCommitmentPoster(final NotificationQueueService notificationQueueService) {
+        this.notificationQueueService = notificationQueueService;
+    }
+
+    public void insertParentInvoiceFromTransactionInternal(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
+                                                           final UUID invoiceId,
+                                                           final DateTime futureNotificationTime,
+                                                           final AccountDateAndTimeZoneContext accountDateAndTimeZoneContext,
+                                                           final InternalCallContext internalCallContext) {
+        final NotificationQueue commitInvoiceQueue;
+        try {
+            commitInvoiceQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+                                                                               ParentInvoiceCommitmentNotifier.PARENT_INVOICE_COMMITMENT_NOTIFIER_QUEUE);
+
+            // If we see existing notification for the same date we don't insert a new notification
+            final List<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> futureNotifications = commitInvoiceQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
+            final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> existingFutureNotificationWithSameDate = Iterables.tryFind(futureNotifications, new Predicate<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>>() {
+                @Override
+                public boolean apply(final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input) {
+
+                    final LocalDate notificationEffectiveLocaleDate = accountDateAndTimeZoneContext.computeLocalDateFromFixedAccountOffset(futureNotificationTime);
+                    final LocalDate eventEffectiveLocaleDate = accountDateAndTimeZoneContext.computeLocalDateFromFixedAccountOffset(input.getEffectiveDate());
+
+                    return notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0;
+                }
+            }).orNull();
+
+            if (existingFutureNotificationWithSameDate == null) {
+                log.info("Queuing parent invoice commitment notification at {} for invoiceId {}", futureNotificationTime.toString(), invoiceId.toString());
+
+                commitInvoiceQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), futureNotificationTime,
+                                                                         new ParentInvoiceCommitmentNotificationKey(invoiceId), internalCallContext.getUserToken(),
+                                                                         internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
+            } else if (log.isDebugEnabled()) {
+                log.debug("*********************   SKIPPING Queuing parent invoice commitment notification at {} for invoiceId {} *******************", futureNotificationTime.toString(), invoiceId.toString());
+            }
+
+        } catch (final NoSuchNotificationQueue e) {
+            log.error("Attempting to put items on a non-existent queue (ParentInvoiceCommitmentNotifier).", e);
+        } catch (final IOException e) {
+            log.error("Failed to serialize notificationKey for invoiceId {}", invoiceId);
+        }
+    }
+
+}
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 813154d..4c780d9 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
@@ -28,6 +28,9 @@ allTableValues() ::= <<
 , <tableValues()>
 >>
 
-
-
-
+getChildInvoicesByParentInvoiceId() ::= <<
+   SELECT <allTableFields()>
+     FROM <tableName()>
+    WHERE parent_invoice_id = :parentInvoiceId
+    <AND_CHECK_TENANT()>
+ >>
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 8c8310f..23650a6 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
@@ -377,4 +377,9 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     public InvoiceModelDao getParentDraftInvoice(final UUID parentAccountId, final InternalCallContext context) throws InvoiceApiException {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(final UUID parentInvoiceId, final InternalCallContext context) throws InvoiceApiException {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 9006337..af3e3a2 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -1723,4 +1723,23 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceModelDao.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));
         invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), context);
     }
+
+    @Test(groups = "slow")
+    public void testCreateParentChildInvoiceRelation() throws InvoiceApiException {
+
+        final UUID parentInvoiceId = UUID.randomUUID();
+        final UUID childInvoiceId = UUID.randomUUID();
+        final UUID childAccountId = UUID.randomUUID();
+        InvoiceParentChildModelDao invoiceRelation = new InvoiceParentChildModelDao(parentInvoiceId, childInvoiceId, childAccountId);
+        invoiceDao.createParentChildInvoiceRelation(invoiceRelation, context);
+
+        final List<InvoiceParentChildModelDao> relations = invoiceDao.getChildInvoicesByParentInvoiceId(parentInvoiceId, context);
+        assertEquals(relations.size(), 1);
+        final InvoiceParentChildModelDao parentChildRelation = relations.get(0);
+        assertEquals(parentChildRelation.getChildAccountId(), childAccountId);
+        assertEquals(parentChildRelation.getChildInvoiceId(), childInvoiceId);
+        assertEquals(parentChildRelation.getParentInvoiceId(), parentInvoiceId);
+
+    }
+
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index 066bd7f..a86eb99 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -31,6 +31,9 @@ import javax.inject.Named;
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
@@ -97,6 +100,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     private final RetryServiceScheduler retryServiceScheduler;
     private final InternalCallContextFactory internalCallContextFactory;
     private final Clock clock;
+    private final AccountInternalApi accountApi;
 
     private final Logger log = LoggerFactory.getLogger(InvoicePaymentControlPluginApi.class);
 
@@ -105,7 +109,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                                           final InvoiceInternalApi invoiceApi, final TagUserApi tagApi,
                                           final PaymentDao paymentDao, final InvoicePaymentControlDao invoicePaymentControlDao,
                                           @Named(PaymentModule.RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
-                                          final InternalCallContextFactory internalCallContextFactory, final Clock clock) {
+                                          final InternalCallContextFactory internalCallContextFactory, final Clock clock,
+                                          final AccountInternalApi accountApi) {
         this.paymentConfig = paymentConfig;
         this.invoiceApi = invoiceApi;
         this.tagApi = tagApi;
@@ -114,6 +119,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         this.retryServiceScheduler = retryServiceScheduler;
         this.internalCallContextFactory = internalCallContextFactory;
         this.clock = clock;
+        this.accountApi = accountApi;
     }
 
     @Override
@@ -263,6 +269,12 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                 return new DefaultPriorPaymentControlResult(true);
             }
 
+            // get immutable account and check if it is child and payment is delegated to parent => abort
+            final ImmutableAccountData accountData = accountApi.getImmutableAccountDataById(invoice.getAccountId(), internalContext);
+            if ((accountData != null) && (accountData.getParentAccountId() != null) && accountData.isPaymentDelegatedToParent()) {
+                return new DefaultPriorPaymentControlResult(true);
+            }
+
             final BigDecimal requestedAmount = validateAndComputePaymentAmount(invoice, paymentControlPluginContext.getAmount(), paymentControlPluginContext.isApiPayment());
 
             final boolean isAborted = requestedAmount.compareTo(BigDecimal.ZERO) == 0;
@@ -283,6 +295,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
             throw new PaymentControlApiException(e);
         } catch (final IllegalArgumentException e) {
             throw new PaymentControlApiException(e);
+        } catch (AccountApiException e) {
+            throw new PaymentControlApiException(e);
         }
     }