killbill-memoizeit

Details

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 426a7d4..6c97c11 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
@@ -393,6 +393,11 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         assertTrue(parentInvoice.isParentInvoice());
         assertEquals(parentInvoice.getBalance().toString(), "279.90");
 
+        // Check Child Balance. It should be > 0 here because Parent didn't pay yet.
+        List<Invoice> child1Invoices = invoiceUserApi.getInvoicesByAccount(child1Account.getId(), callContext);
+        assertEquals(child1Invoices.size(), 2);
+        assertTrue(child1Invoices.get(1).getBalance().compareTo(BigDecimal.ZERO) > 0);
+
         // Moving a day the NotificationQ calls the commitInvoice. Now payment is expected
         busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
         clock.addDays(1);
@@ -401,6 +406,12 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
         parentInvoice = invoiceUserApi.getInvoice(parentInvoice.getId(), callContext);
         assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
 
+        // Check Child Balance. It should be = 0 because parent invoice had already paid.
+        child1Invoices = invoiceUserApi.getInvoicesByAccount(child1Account.getId(), callContext);
+        assertEquals(child1Invoices.size(), 2);
+        assertTrue(parentInvoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
+        assertTrue(child1Invoices.get(1).getBalance().compareTo(BigDecimal.ZERO) == 0);
+
     }
 
     @Test(groups = "slow")
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 39a7120..3e3c4af 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
@@ -48,6 +48,7 @@ import org.killbill.billing.invoice.api.InvoicePaymentType;
 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.model.DefaultInvoice;
 import org.killbill.billing.invoice.notification.NextBillingDatePoster;
 import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
 import org.killbill.billing.util.AccountDateAndTimeZoneContext;
@@ -394,7 +395,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 BigDecimal accountBalance = BigDecimal.ZERO;
                 final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(entitySqlDaoWrapperFactory, context);
                 for (final InvoiceModelDao cur : invoices) {
-                    accountBalance = accountBalance.add(InvoiceModelDaoHelper.getBalance(cur));
+                    accountBalance = accountBalance.add((new DefaultInvoice(cur)).getBalance());
                     cba = cba.add(InvoiceModelDaoHelper.getCBAAmount(cur));
                 }
                 return accountBalance.subtract(cba);
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 b4254f6..1e4bf33 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
@@ -40,6 +40,7 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoiceStatus;
 import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
@@ -57,10 +58,12 @@ public class InvoiceDaoHelper {
     private static final Logger log = LoggerFactory.getLogger(InvoiceDaoHelper.class);
 
     private final TagInternalApi tagInternalApi;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
-    public InvoiceDaoHelper(final TagInternalApi tagInternalApi) {
+    public InvoiceDaoHelper(final TagInternalApi tagInternalApi, final InternalCallContextFactory internalCallContextFactory) {
         this.tagInternalApi = tagInternalApi;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     /**
@@ -226,12 +229,14 @@ public class InvoiceDaoHelper {
         getInvoiceItemsWithinTransaction(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
         getInvoicePaymentsWithinTransaction(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
         setInvoiceWrittenOff(invoice, context);
+        getParentInvoice(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
     }
 
     public void populateChildren(final Iterable<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
         getInvoiceItemsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
         getInvoicePaymentsWithinTransaction(invoices, entitySqlDaoWrapperFactory, context);
         setInvoicesWrittenOff(invoices, context);
+        getParentInvoice(invoices, entitySqlDaoWrapperFactory, context);
     }
 
     public List<InvoiceModelDao> getAllInvoicesByAccountFromTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
@@ -325,4 +330,19 @@ public class InvoiceDaoHelper {
         });
     }
 
+    private void getParentInvoice(final Iterable<InvoiceModelDao> invoices, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext internalTenantContext) {
+
+        final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
+        for (InvoiceModelDao invoice : invoices) {
+            if (invoice.isParentInvoice()) continue;
+            final InvoiceModelDao parentInvoice = invoiceSqlDao.getParentInvoiceByChildInvoiceId(invoice.getId().toString(), internalTenantContext);
+            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 ce4da0d..589d1c7 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
@@ -26,7 +26,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceStatus;
@@ -44,12 +43,13 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
     private Currency currency;
     private boolean migrated;
     private InvoiceStatus status;
-    private boolean parentInvoice;
+    private boolean isParentInvoice;
 
     // Not in the database, for convenience only
     private List<InvoiceItemModelDao> invoiceItems = new LinkedList<InvoiceItemModelDao>();
     private List<InvoicePaymentModelDao> invoicePayments = new LinkedList<InvoicePaymentModelDao>();
     private Currency processedCurrency;
+    private InvoiceModelDao parentInvoice;
 
     private boolean isWrittenOff;
 
@@ -57,7 +57,7 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
 
     public InvoiceModelDao(final UUID id, @Nullable final DateTime createdDate, final UUID accountId,
                            @Nullable final Integer invoiceNumber, final LocalDate invoiceDate, final LocalDate targetDate,
-                           final Currency currency, final boolean migrated, final InvoiceStatus status, final boolean parentInvoice) {
+                           final Currency currency, final boolean migrated, final InvoiceStatus status, final boolean isParentInvoice) {
         super(id, createdDate, createdDate);
         this.accountId = accountId;
         this.invoiceNumber = invoiceNumber;
@@ -67,7 +67,7 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         this.migrated = migrated;
         this.isWrittenOff = false;
         this.status = status;
-        this.parentInvoice = parentInvoice;
+        this.isParentInvoice = isParentInvoice;
     }
 
     public InvoiceModelDao(final UUID accountId, final LocalDate invoiceDate, final LocalDate targetDate, final Currency currency, final boolean migrated) {
@@ -148,7 +148,7 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
     }
 
     public boolean isParentInvoice() {
-        return parentInvoice;
+        return isParentInvoice;
     }
 
     public void setAccountId(final UUID accountId) {
@@ -195,10 +195,18 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         this.status = status;
     }
 
-    public void setParentInvoice(final boolean parentInvoice) {
+    public void setParentInvoice(final boolean isParentInvoice) {
+        this.isParentInvoice = isParentInvoice;
+    }
+
+    public void addParentInvoice(InvoiceModelDao parentInvoice) {
         this.parentInvoice = parentInvoice;
     }
 
+    public InvoiceModelDao getParentInvoice() {
+        return parentInvoice;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("InvoiceModelDao{");
@@ -213,6 +221,7 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         sb.append(", invoicePayments=").append(invoicePayments);
         sb.append(", processedCurrency=").append(processedCurrency);
         sb.append(", isWrittenOff=").append(isWrittenOff);
+        sb.append(", isParentInvoice=").append(isParentInvoice);
         sb.append(", parentInvoice=").append(parentInvoice);
         sb.append('}');
         return sb.toString();
@@ -262,7 +271,10 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         if (invoicePayments != null ? !invoicePayments.equals(that.invoicePayments) : that.invoicePayments != null) {
             return false;
         }
-        if (parentInvoice != that.parentInvoice) {
+        if (isParentInvoice != that.isParentInvoice) {
+            return false;
+        }
+        if (parentInvoice != null ? !parentInvoice.equals(that.parentInvoice) : that.parentInvoice != null) {
             return false;
         }
         return processedCurrency == that.processedCurrency;
@@ -282,7 +294,8 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         result = 31 * result + (invoicePayments != null ? invoicePayments.hashCode() : 0);
         result = 31 * result + (processedCurrency != null ? processedCurrency.hashCode() : 0);
         result = 31 * result + (isWrittenOff ? 1 : 0);
-        result = 31 * result + (parentInvoice ? 1 : 0);
+        result = 31 * result + (isParentInvoice ? 1 : 0);
+        result = 31 * result + (parentInvoice != null ? parentInvoice.hashCode() : 0);
         return result;
     }
 
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 1907037..f4a4b98 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
@@ -51,5 +51,9 @@ public interface InvoiceSqlDao extends EntitySqlDao<InvoiceModelDao, Invoice> {
     @SqlQuery
     InvoiceModelDao getParentDraftInvoice(@Bind("accountId") final String parentAccountId,
                                @BindBean final InternalTenantContext context);
+
+    @SqlQuery
+    InvoiceModelDao getParentInvoiceByChildInvoiceId(@Bind("childInvoiceId") final String childInvoiceId,
+                                                     @BindBean final InternalTenantContext context);
 }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
index a9c7f06..454c33f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
@@ -26,7 +26,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.invoice.api.Invoice;
@@ -56,7 +55,8 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
 
     private final Currency processedCurrency;
     private final InvoiceStatus status;
-    private final boolean parentInvoice;
+    private final boolean isParentInvoice;
+    private final Invoice parentInvoice;
 
 
     // Used to create a new invoice
@@ -70,7 +70,7 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
 
     public DefaultInvoice(final UUID invoiceId, final UUID accountId, @Nullable final Integer invoiceNumber, final LocalDate invoiceDate,
                           final LocalDate targetDate, final Currency currency, final boolean isMigrationInvoice, final InvoiceStatus status) {
-        this(invoiceId, null, accountId, invoiceNumber, invoiceDate, targetDate, currency, currency, isMigrationInvoice, false, status, false);
+        this(invoiceId, null, accountId, invoiceNumber, invoiceDate, targetDate, currency, currency, isMigrationInvoice, false, status, false, null);
     }
 
 
@@ -79,7 +79,8 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
         this(invoiceModelDao.getId(), invoiceModelDao.getCreatedDate(), invoiceModelDao.getAccountId(),
              invoiceModelDao.getInvoiceNumber(), invoiceModelDao.getInvoiceDate(), invoiceModelDao.getTargetDate(),
              invoiceModelDao.getCurrency(), invoiceModelDao.getProcessedCurrency(), invoiceModelDao.isMigrated(),
-             invoiceModelDao.isWrittenOff(), invoiceModelDao.getStatus(), invoiceModelDao.isParentInvoice());
+             invoiceModelDao.isWrittenOff(), invoiceModelDao.getStatus(), invoiceModelDao.isParentInvoice(),
+             invoiceModelDao.getParentInvoice());
         addInvoiceItems(Collections2.transform(invoiceModelDao.getInvoiceItems(), new Function<InvoiceItemModelDao, InvoiceItem>() {
             @Override
             public InvoiceItem apply(final InvoiceItemModelDao input) {
@@ -96,14 +97,14 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
 
     // Used to create a new parent invoice
     public DefaultInvoice(final UUID accountId, final LocalDate invoiceDate, final Currency currency) {
-        this(UUID.randomUUID(), null, accountId, null, invoiceDate, null, currency, currency, false, false, InvoiceStatus.DRAFT, true);
+        this(UUID.randomUUID(), null, accountId, null, invoiceDate, null, currency, currency, false, false, InvoiceStatus.DRAFT, true, null);
     }
 
     private DefaultInvoice(final UUID invoiceId, @Nullable final DateTime createdDate, final UUID accountId,
                            @Nullable final Integer invoiceNumber, final LocalDate invoiceDate,
                            @Nullable final LocalDate targetDate, final Currency currency, final Currency processedCurrency,
                            final boolean isMigrationInvoice, final boolean isWrittenOff,
-                           final InvoiceStatus status, final boolean parentInvoice) {
+                           final InvoiceStatus status, final boolean isParentInvoice, final InvoiceModelDao parentInvoice) {
         super(invoiceId, createdDate, createdDate);
         this.accountId = accountId;
         this.invoiceNumber = invoiceNumber;
@@ -116,14 +117,16 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
         this.invoiceItems = new ArrayList<InvoiceItem>();
         this.payments = new ArrayList<InvoicePayment>();
         this.status = status;
-        this.parentInvoice = parentInvoice;
+        this.isParentInvoice = isParentInvoice;
+        this.parentInvoice = (parentInvoice != null) ? new DefaultInvoice(parentInvoice) : null;
     }
 
 
     // Semi deep copy where we copy the lists but not the elements in the lists since they are immutables.
     @Override
     public Object clone() {
-        final Invoice clonedInvoice = new DefaultInvoice(getId(),  getCreatedDate(), getAccountId(), getInvoiceNumber(), getInvoiceDate(), getTargetDate(), getCurrency(), getProcessedCurrency(), isMigrationInvoice(), isWrittenOff(), getStatus(), isParentInvoice());
+        InvoiceModelDao parentInvoiceModelDao = (parentInvoice != null) ? new InvoiceModelDao(parentInvoice) : null;
+        final Invoice clonedInvoice = new DefaultInvoice(getId(),  getCreatedDate(), getAccountId(), getInvoiceNumber(), getInvoiceDate(), getTargetDate(), getCurrency(), getProcessedCurrency(), isMigrationInvoice(), isWrittenOff(), getStatus(), isParentInvoice(), parentInvoiceModelDao);
         clonedInvoice.getInvoiceItems().addAll(getInvoiceItems());
         clonedInvoice.getPayments().addAll(getPayments());
         return clonedInvoice;
@@ -246,7 +249,11 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
 
     @Override
     public BigDecimal getBalance() {
-        return isWrittenOff ? BigDecimal.ZERO : InvoiceCalculatorUtils.computeInvoiceBalance(currency, invoiceItems, payments);
+        return isWrittenOff || hasZeroParentBalance() ? BigDecimal.ZERO : InvoiceCalculatorUtils.computeInvoiceBalance(currency, invoiceItems, payments);
+    }
+
+    private boolean hasZeroParentBalance() {
+        return (parentInvoice != null) && (parentInvoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
     }
 
     public boolean isWrittenOff() {
@@ -260,14 +267,14 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
 
     @Override
     public boolean isParentInvoice() {
-        return parentInvoice;
+        return isParentInvoice;
     }
 
     @Override
     public String toString() {
         return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId
                + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getPaidAmount()
-               + ", status=" + status + ", isParentInvoice=" + parentInvoice + "]";
+               + ", status=" + status + ", isParentInvoice=" + isParentInvoice + "]";
     }
 
 }
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 dc26465..02de5dc 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
@@ -69,4 +69,13 @@ getParentDraftInvoice() ::= <<
      AND status = 'DRAFT'
    <AND_CHECK_TENANT()>
    <defaultOrderBy()>
->>
\ No newline at end of file
+>>
+
+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