killbill-memoizeit
Changes
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java 29(+11 -18)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java 4(+3 -1)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustedCBAInvoiceItem.java 136(+136 -0)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustmentInvoiceItemForRepair.java 135(+135 -0)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java 367(+89 -278)
Details
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
index 5a77d36..665addc 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceAndInvoicePaymentDao.java
@@ -19,7 +19,6 @@ package com.ning.billing.osgi.bundles.analytics.dao;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
@@ -41,7 +40,9 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
/**
* Wrapper around BusinessInvoiceDao and BusinessInvoicePaymentDao.
@@ -77,8 +78,8 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
// Recompute invoice, invoice items and invoice payments records
final Map<UUID, BusinessInvoiceModelDao> invoices = new HashMap<UUID, BusinessInvoiceModelDao>();
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
- final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments = new HashMap<UUID, Collection<BusinessInvoicePaymentBaseModelDao>>();
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> invoiceItems = ArrayListMultimap.<UUID, BusinessInvoiceItemBaseModelDao>create();
+ final Multimap<UUID, BusinessInvoicePaymentBaseModelDao> invoicePayments = ArrayListMultimap.<UUID, BusinessInvoicePaymentBaseModelDao>create();
createBusinessPojos(accountId, invoices, invoiceItems, invoicePayments, context);
// Delete and recreate all items in the transaction
@@ -94,8 +95,8 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
@VisibleForTesting
void createBusinessPojos(final UUID accountId,
final Map<UUID, BusinessInvoiceModelDao> invoices,
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems,
- final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments,
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> invoiceItems,
+ final Multimap<UUID, BusinessInvoicePaymentBaseModelDao> invoicePayments,
final CallContext context) throws AnalyticsRefreshException {
// Recompute all invoices and invoice items. Invoices will have their denormalized payment fields missing,
// and items won't have neither invoice nor payment denormalized fields populated
@@ -107,17 +108,9 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
// Transform the results
for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.keySet()) {
invoices.put(businessInvoice.getInvoiceId(), businessInvoice);
- for (final BusinessInvoiceItemBaseModelDao businessInvoiceItem : businessInvoices.get(businessInvoice)) {
- if (invoiceItems.get(businessInvoice.getInvoiceId()) == null) {
- invoiceItems.put(businessInvoice.getInvoiceId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
- }
- invoiceItems.get(businessInvoice.getInvoiceId()).add(businessInvoiceItem);
- }
+ invoiceItems.get(businessInvoice.getInvoiceId()).addAll(businessInvoices.get(businessInvoice));
}
for (final BusinessInvoicePaymentBaseModelDao businessInvoicePayment : businessInvoicePayments) {
- if (invoicePayments.get(businessInvoicePayment.getInvoiceId()) == null) {
- invoicePayments.put(businessInvoicePayment.getInvoiceId(), new LinkedList<BusinessInvoicePaymentBaseModelDao>());
- }
invoicePayments.get(businessInvoicePayment.getInvoiceId()).add(businessInvoicePayment);
}
@@ -126,8 +119,8 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
}
private void populatedMissingDenormalizedFields(final Map<UUID, BusinessInvoiceModelDao> businessInvoices,
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems,
- final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> businessInvoicePayments) {
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
+ final Multimap<UUID, BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
// First, populated missing payment fields in invoice
for (final BusinessInvoiceModelDao businessInvoice : businessInvoices.values()) {
final BigDecimal balance = BusinessInvoiceUtils.computeInvoiceBalance(businessInvoiceItems.get(businessInvoice.getInvoiceId()),
@@ -171,8 +164,8 @@ public class BusinessInvoiceAndInvoicePaymentDao extends BusinessAnalyticsDaoBas
*/
private void updateInTransaction(final BusinessAccountModelDao bac,
final Map<UUID, BusinessInvoiceModelDao> invoices,
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems,
- final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments,
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> invoiceItems,
+ final Multimap<UUID, BusinessInvoicePaymentBaseModelDao> invoicePayments,
final BusinessAnalyticsSqlDao transactional,
final CallContext context) {
// Update invoice and invoice items tables
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
index fe3f936..3a5759f 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/BusinessInvoiceDao.java
@@ -26,6 +26,8 @@ import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceModelDao
import com.ning.billing.util.callcontext.CallContext;
import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
+import com.google.common.collect.Multimap;
+
public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
public BusinessInvoiceDao(final OSGIKillbillDataSource osgiKillbillDataSource) {
@@ -43,7 +45,7 @@ public class BusinessInvoiceDao extends BusinessAnalyticsDaoBase {
*/
public void updateInTransaction(final BusinessAccountModelDao bac,
final Map<UUID, BusinessInvoiceModelDao> businessInvoices,
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItems,
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
final BusinessAnalyticsSqlDao transactional,
final CallContext context) {
deleteInvoicesAndInvoiceItemsForAccountInTransaction(transactional, bac.getAccountRecordId(), bac.getTenantRecordId(), context);
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustedCBAInvoiceItem.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustedCBAInvoiceItem.java
new file mode 100644
index 0000000..e5d7376
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustedCBAInvoiceItem.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.osgi.bundles.analytics.dao.factory;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class AdjustedCBAInvoiceItem implements InvoiceItem {
+
+ private final InvoiceItem cbaInvoiceItem;
+ private final BigDecimal amount;
+ private final UUID reparationItemId;
+
+ public AdjustedCBAInvoiceItem(final InvoiceItem cbaInvoiceItem,
+ final BigDecimal amount,
+ final UUID reparationItemId) {
+ this.cbaInvoiceItem = cbaInvoiceItem;
+ this.amount = amount;
+ this.reparationItemId = reparationItemId;
+ }
+
+ @Override
+ public InvoiceItemType getInvoiceItemType() {
+ return InvoiceItemType.CBA_ADJ;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return cbaInvoiceItem.getInvoiceId();
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return cbaInvoiceItem.getAccountId();
+ }
+
+ @Override
+ public LocalDate getStartDate() {
+ return cbaInvoiceItem.getStartDate();
+ }
+
+ @Override
+ public LocalDate getEndDate() {
+ return cbaInvoiceItem.getStartDate();
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return cbaInvoiceItem.getCurrency();
+ }
+
+ @Override
+ public String getDescription() {
+ return cbaInvoiceItem.getDescription();
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return cbaInvoiceItem.getBundleId();
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return cbaInvoiceItem.getSubscriptionId();
+ }
+
+ @Override
+ public String getPlanName() {
+ return cbaInvoiceItem.getPlanName();
+ }
+
+ @Override
+ public String getPhaseName() {
+ return cbaInvoiceItem.getPhaseName();
+ }
+
+ @Override
+ public BigDecimal getRate() {
+ return cbaInvoiceItem.getRate();
+ }
+
+ @Override
+ public UUID getLinkedItemId() {
+ return cbaInvoiceItem.getLinkedItemId();
+ }
+
+ @Override
+ public boolean matches(final Object other) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ return cbaInvoiceItem.getId();
+ }
+
+ public UUID getSecondId() {
+ return reparationItemId;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return cbaInvoiceItem.getCreatedDate();
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return cbaInvoiceItem.getUpdatedDate();
+ }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustmentInvoiceItemForRepair.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustmentInvoiceItemForRepair.java
new file mode 100644
index 0000000..2be13e5
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/AdjustmentInvoiceItemForRepair.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.osgi.bundles.analytics.dao.factory;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class AdjustmentInvoiceItemForRepair implements InvoiceItem {
+
+ private final InvoiceItem repairInvoiceItem;
+ private final InvoiceItem reparationInvoiceItem;
+
+ public AdjustmentInvoiceItemForRepair(final InvoiceItem repairInvoiceItem,
+ final InvoiceItem reparationInvoiceItem) {
+ this.repairInvoiceItem = repairInvoiceItem;
+ this.reparationInvoiceItem = reparationInvoiceItem;
+ }
+
+ @Override
+ public InvoiceItemType getInvoiceItemType() {
+ return InvoiceItemType.ITEM_ADJ;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return repairInvoiceItem.getInvoiceId();
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return repairInvoiceItem.getAccountId();
+ }
+
+ @Override
+ public LocalDate getStartDate() {
+ return repairInvoiceItem.getStartDate();
+ }
+
+ @Override
+ public LocalDate getEndDate() {
+ return repairInvoiceItem.getStartDate();
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return reparationInvoiceItem.getAmount().add(repairInvoiceItem.getAmount());
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return repairInvoiceItem.getCurrency();
+ }
+
+ @Override
+ public String getDescription() {
+ return null;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ return null;
+ }
+
+ @Override
+ public UUID getSubscriptionId() {
+ return null;
+ }
+
+ @Override
+ public String getPlanName() {
+ return null;
+ }
+
+ @Override
+ public String getPhaseName() {
+ return null;
+ }
+
+ @Override
+ public BigDecimal getRate() {
+ return null;
+ }
+
+ @Override
+ public UUID getLinkedItemId() {
+ return repairInvoiceItem.getLinkedItemId();
+ }
+
+ @Override
+ public boolean matches(final Object other) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ // We pretend to be the repair, the reparation item record id
+ // will be available as secondId
+ return repairInvoiceItem.getId();
+ }
+
+ public UUID getSecondId() {
+ return reparationInvoiceItem.getId();
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return repairInvoiceItem.getCreatedDate();
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return repairInvoiceItem.getUpdatedDate();
+ }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
index 4f8a46b..1dafc74 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
@@ -28,12 +28,9 @@ import java.util.UUID;
import javax.annotation.Nullable;
-import org.joda.time.DateTime;
-import org.joda.time.LocalDate;
import org.osgi.service.log.LogService;
import com.ning.billing.account.api.Account;
-import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -55,12 +52,15 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
+import com.google.common.collect.Multimap;
import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isAccountCreditItem;
import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isCharge;
import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isInvoiceAdjustmentItem;
import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isInvoiceItemAdjustmentItem;
+import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isRepareeItemForRepairedItem;
import static com.ning.billing.osgi.bundles.analytics.utils.BusinessInvoiceUtils.isRevenueRecognizable;
public class BusinessInvoiceFactory extends BusinessFactoryBase {
@@ -71,8 +71,10 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
}
/**
- * Create business invoices and invoice items to record. Note that these POJOs are incomplete
- * (denormalized payment fields have not yet been populated)
+ * Create current business invoices and invoice items.
+ * <p/>
+ * Note that these POJOs are incomplete (denormalized payment fields have not yet been populated, and denormalized
+ * invoice fields in business invoice items have not been populated either).
*
* @param accountId current accountId refreshed
* @param context call context
@@ -90,45 +92,48 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
// Lookup the invoices for that account
final Collection<Invoice> invoices = getInvoicesByAccountId(account.getId(), context);
- // All invoice items across all invoices for that accounr (we need to be able to reference items across multiple invoices)
+ // All invoice items across all invoices for that account (we need to be able to reference items across multiple invoices)
final Collection<InvoiceItem> allInvoiceItems = new LinkedList<InvoiceItem>();
- // Convenient mapping invoice_id -> invoice
+ // Convenient mapping invoiceId -> invoice
final Map<UUID, Invoice> invoiceIdToInvoiceMappings = new LinkedHashMap<UUID, Invoice>();
for (final Invoice invoice : invoices) {
invoiceIdToInvoiceMappings.put(invoice.getId(), invoice);
allInvoiceItems.addAll(invoice.getInvoiceItems());
}
+ // *** MAGIC HAPPENS HERE ***
// Sanitize (cherry-pick, merge) the items
final Collection<InvoiceItem> sanitizedInvoiceItems = sanitizeInvoiceItems(allInvoiceItems);
+ // *** MAGIC HAPPENS HERE ***
- // Create the business invoice items. These are incomplete (the denormalized invoice fields haven't been computed yet)
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> businessInvoiceItemsForInvoiceId = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
+ // Create the business invoice items. These are incomplete: the denormalized invoice fields can't be computed yet,
+ // since we need all business invoice items to do it.
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> businessInvoiceItemsForInvoiceId = ArrayListMultimap.<UUID, BusinessInvoiceItemBaseModelDao>create();
for (final InvoiceItem invoiceItem : sanitizedInvoiceItems) {
final Invoice invoice = invoiceIdToInvoiceMappings.get(invoiceItem.getInvoiceId());
+ final Collection<InvoiceItem> otherInvoiceItems = Collections2.filter(sanitizedInvoiceItems,
+ new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return !input.getId().equals(invoiceItem.getId());
+ }
+ });
final BusinessInvoiceItemBaseModelDao businessInvoiceItem = createBusinessInvoiceItem(account,
invoice,
invoiceItem,
- Collections2.filter(sanitizedInvoiceItems,
- new Predicate<InvoiceItem>() {
- @Override
- public boolean apply(final InvoiceItem input) {
- return !input.getId().equals(invoiceItem.getId());
- }
- }),
+ otherInvoiceItems,
accountRecordId,
tenantRecordId,
reportGroup,
context);
if (businessInvoiceItem != null) {
- if (businessInvoiceItemsForInvoiceId.get(invoice.getId()) == null) {
- businessInvoiceItemsForInvoiceId.put(invoice.getId(), new LinkedList<BusinessInvoiceItemBaseModelDao>());
- }
businessInvoiceItemsForInvoiceId.get(invoice.getId()).add(businessInvoiceItem);
}
}
- // Now, create the business invoices
+ // Now, create the business invoices. We needed the final business invoice items to compute the various invoice amounts. At this point,
+ // we could go back and populate the denormalized invoice amounts in the various items, but since we need to do a second pass later
+ // to populate the denormalized payment fields, we'll hold off for now.
final Map<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>> businessRecords = new HashMap<BusinessInvoiceModelDao, Collection<BusinessInvoiceItemBaseModelDao>>();
for (final Invoice invoice : invoices) {
final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems = businessInvoiceItemsForInvoiceId.get(invoice.getId());
@@ -136,7 +141,13 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
continue;
}
- final BusinessInvoiceModelDao businessInvoice = createBusinessInvoice(account, invoice, businessInvoiceItems, accountRecordId, tenantRecordId, reportGroup, context);
+ final BusinessInvoiceModelDao businessInvoice = createBusinessInvoice(account,
+ invoice,
+ businessInvoiceItems,
+ accountRecordId,
+ tenantRecordId,
+ reportGroup,
+ context);
businessRecords.put(businessInvoice, businessInvoiceItems);
}
@@ -266,13 +277,50 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
/**
* Filter and transform the original invoice items for Analytics purposes. We mainly
- * merge REPAIR_ADJ items with reparation items (reparees) to create item adjustments.
+ * merge REPAIR_ADJ items with reparation items (reparees) to create item adjustments
+ * and merge CBA items.
*
* @param allInvoiceItems all items for the current account
* @return invoice items interesting for Analytics purposes
*/
@VisibleForTesting
Collection<InvoiceItem> sanitizeInvoiceItems(final Collection<InvoiceItem> allInvoiceItems) {
+ // First, find all reparee items, to be able to merge REPAIR_ADJ and reparee items into ITEM_ADJ items
+ final Map<UUID, InvoiceItem> repareeInvoiceItemIdToRepairItemMappings = findRepareeInvoiceItems(allInvoiceItems);
+
+ // Second, since we are going to rebalance some items (the reparee items are going to be on a previous invoice),
+ // we need to rebalance CBA_ADJ items. In order to simplify the process, we merge all items per invoice and revenueRecognizable
+ // status (this information should be enough for financial and analytics reporting purposes).
+ final Collection<AdjustedCBAInvoiceItem> mergedCBAItems = buildMergedCBAItems(allInvoiceItems, repareeInvoiceItemIdToRepairItemMappings);
+
+ // Filter the invoice items for analytics
+ final Collection<InvoiceItem> invoiceItemsForAnalytics = new LinkedList<InvoiceItem>();
+ for (final InvoiceItem invoiceItem : allInvoiceItems) {
+ if (InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+ // We don't care, we'll merge them on all invoices
+ } else if (InvoiceItemType.REPAIR_ADJ.equals(invoiceItem.getInvoiceItemType())) {
+ // We don't care, we'll create a special item for it below
+ } else if (repareeInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
+ // We do care - this is a reparation item. Create an item adjustment for it
+ final InvoiceItem repairInvoiceItem = repareeInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
+ final InvoiceItem reparationInvoiceItem = invoiceItem;
+ invoiceItemsForAnalytics.add(new AdjustmentInvoiceItemForRepair(repairInvoiceItem, reparationInvoiceItem));
+ } else {
+ invoiceItemsForAnalytics.add(invoiceItem);
+ }
+ }
+ invoiceItemsForAnalytics.addAll(mergedCBAItems);
+
+ return invoiceItemsForAnalytics;
+ }
+
+ /**
+ * Find all reparee items
+ *
+ * @param allInvoiceItems all invoice items, across all invoices
+ * @return a mapping reparee invoice item id to REPAIR_ADJ item
+ */
+ private Map<UUID, InvoiceItem> findRepareeInvoiceItems(final Collection<InvoiceItem> allInvoiceItems) {
// Build a convenience mapping between items -> repair_adj items (inverse of linkedItemId)
final Map<UUID, InvoiceItem> repairedInvoiceItemIdToRepairInvoiceItemMappings = new HashMap<UUID, InvoiceItem>();
for (final InvoiceItem invoiceItem : allInvoiceItems) {
@@ -281,39 +329,38 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
}
}
- // Now find the "reparation" items, i.e. the ones which correspond to the repaired items
- final Map<UUID, InvoiceItem> reparationInvoiceItemIdToRepairItemMappings = new LinkedHashMap<UUID, InvoiceItem>();
+ // Now find the "reparee" items, i.e. the ones which correspond to the repaired items
+ final Map<UUID, InvoiceItem> repareeInvoiceItemIdToRepairItemMappings = new LinkedHashMap<UUID, InvoiceItem>();
for (final InvoiceItem repairedInvoiceItem : allInvoiceItems) {
// Skip non-repaired items
if (!repairedInvoiceItemIdToRepairInvoiceItemMappings.keySet().contains(repairedInvoiceItem.getId())) {
continue;
}
- InvoiceItem reparationItem = null;
+ InvoiceItem repareeItem = null;
for (final InvoiceItem invoiceItem : allInvoiceItems) {
- // Try to find the matching "reparation" item
- if (repairedInvoiceItem.getInvoiceItemType().equals(invoiceItem.getInvoiceItemType()) &&
- repairedInvoiceItem.getSubscriptionId().equals(invoiceItem.getSubscriptionId()) &&
- repairedInvoiceItem.getStartDate().compareTo(invoiceItem.getStartDate()) == 0 &&
- // FIXED items have a null end date
- ((repairedInvoiceItem.getEndDate() == null && invoiceItem.getEndDate() == null) ||
- (repairedInvoiceItem.getEndDate() != null && invoiceItem.getEndDate() != null && !repairedInvoiceItem.getEndDate().isBefore(invoiceItem.getEndDate()))) &&
- !repairedInvoiceItem.getId().equals(invoiceItem.getId())) {
- if (reparationItem == null) {
- reparationItem = invoiceItem;
+ // Try to find the matching "reparee" item
+ if (isRepareeItemForRepairedItem(repairedInvoiceItem, invoiceItem)) {
+ if (repareeItem == null) {
+ repareeItem = invoiceItem;
} else {
- logService.log(LogService.LOG_ERROR, "Found multiple reparation items matching the repair item id " + repairedInvoiceItem.getId() + " - this should never happen!");
+ logService.log(LogService.LOG_ERROR, "Found multiple reparee items matching the repair item id " + repairedInvoiceItem.getId() + " - this should never happen!");
}
}
}
- if (reparationItem != null) {
- reparationInvoiceItemIdToRepairItemMappings.put(reparationItem.getId(), repairedInvoiceItemIdToRepairInvoiceItemMappings.get(repairedInvoiceItem.getId()));
+ if (repareeItem != null) {
+ repareeInvoiceItemIdToRepairItemMappings.put(repareeItem.getId(), repairedInvoiceItemIdToRepairInvoiceItemMappings.get(repairedInvoiceItem.getId()));
} else {
- logService.log(LogService.LOG_ERROR, "Could not find the reparation item for the repair item id " + repairedInvoiceItem.getId() + " - this should never happen!");
+ logService.log(LogService.LOG_ERROR, "Could not find the reparee item for the repair item id " + repairedInvoiceItem.getId() + " - this should never happen!");
}
}
+ return repareeInvoiceItemIdToRepairItemMappings;
+ }
+
+ private Collection<AdjustedCBAInvoiceItem> buildMergedCBAItems(final Collection<InvoiceItem> allInvoiceItems,
+ final Map<UUID, InvoiceItem> repareeInvoiceItemIdToRepairItemMappings) {
// We now need to adjust the CBA_ADJ for the repair items
final Set<UUID> cbasToIgnore = new HashSet<UUID>();
final Collection<AdjustedCBAInvoiceItem> newCbasToAdd = new LinkedList<AdjustedCBAInvoiceItem>();
@@ -323,8 +370,8 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
}
for (final InvoiceItem invoiceItem : allInvoiceItems) {
- if (reparationInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
- final InvoiceItem repairInvoiceItem = reparationInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
+ if (repareeInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
+ final InvoiceItem repairInvoiceItem = repareeInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
final InvoiceItem reparationInvoiceItem = invoiceItem;
// Au petit bonheur la chance... There is nothing else against to compare
if (repairInvoiceItem.getAmount().negate().compareTo(cbaInvoiceItem.getAmount()) == 0) {
@@ -351,242 +398,6 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
}
}
-
- // Filter the invoice items for analytics
- final Collection<InvoiceItem> invoiceItemsForAnalytics = new LinkedList<InvoiceItem>();
- for (final InvoiceItem invoiceItem : allInvoiceItems) {
- if (cbasToIgnore.contains(invoiceItem.getId())) {
- // We don't care
- } else if (InvoiceItemType.REPAIR_ADJ.equals(invoiceItem.getInvoiceItemType())) {
- // We don't care, we'll create a special item for it below
- } else if (reparationInvoiceItemIdToRepairItemMappings.keySet().contains(invoiceItem.getId())) {
- // We do care - this is a reparation item. Create an item adjustment for it
- final InvoiceItem repairInvoiceItem = reparationInvoiceItemIdToRepairItemMappings.get(invoiceItem.getId());
- final InvoiceItem reparationInvoiceItem = invoiceItem;
- invoiceItemsForAnalytics.add(new AdjustmentInvoiceItemForRepair(repairInvoiceItem, reparationInvoiceItem));
- } else {
- invoiceItemsForAnalytics.add(invoiceItem);
- }
- }
- invoiceItemsForAnalytics.addAll(newCbasToAdd);
-
- return invoiceItemsForAnalytics;
- }
-
- private class AdjustedCBAInvoiceItem implements InvoiceItem {
-
- private final InvoiceItem cbaInvoiceItem;
- private final BigDecimal amount;
- private final UUID reparationItemId;
-
- private AdjustedCBAInvoiceItem(final InvoiceItem cbaInvoiceItem,
- final BigDecimal amount,
- final UUID reparationItemId) {
- this.cbaInvoiceItem = cbaInvoiceItem;
- this.amount = amount;
- this.reparationItemId = reparationItemId;
- }
-
- @Override
- public InvoiceItemType getInvoiceItemType() {
- return InvoiceItemType.CBA_ADJ;
- }
-
- @Override
- public UUID getInvoiceId() {
- return cbaInvoiceItem.getInvoiceId();
- }
-
- @Override
- public UUID getAccountId() {
- return cbaInvoiceItem.getAccountId();
- }
-
- @Override
- public LocalDate getStartDate() {
- return cbaInvoiceItem.getStartDate();
- }
-
- @Override
- public LocalDate getEndDate() {
- return cbaInvoiceItem.getStartDate();
- }
-
- @Override
- public BigDecimal getAmount() {
- return amount;
- }
-
- @Override
- public Currency getCurrency() {
- return cbaInvoiceItem.getCurrency();
- }
-
- @Override
- public String getDescription() {
- return cbaInvoiceItem.getDescription();
- }
-
- @Override
- public UUID getBundleId() {
- return cbaInvoiceItem.getBundleId();
- }
-
- @Override
- public UUID getSubscriptionId() {
- return cbaInvoiceItem.getSubscriptionId();
- }
-
- @Override
- public String getPlanName() {
- return cbaInvoiceItem.getPlanName();
- }
-
- @Override
- public String getPhaseName() {
- return cbaInvoiceItem.getPhaseName();
- }
-
- @Override
- public BigDecimal getRate() {
- return cbaInvoiceItem.getRate();
- }
-
- @Override
- public UUID getLinkedItemId() {
- return cbaInvoiceItem.getLinkedItemId();
- }
-
- @Override
- public boolean matches(final Object other) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public UUID getId() {
- return cbaInvoiceItem.getId();
- }
-
- public UUID getSecondId() {
- return reparationItemId;
- }
-
- @Override
- public DateTime getCreatedDate() {
- return cbaInvoiceItem.getCreatedDate();
- }
-
- @Override
- public DateTime getUpdatedDate() {
- return cbaInvoiceItem.getUpdatedDate();
- }
- }
-
- private class AdjustmentInvoiceItemForRepair implements InvoiceItem {
-
- private final InvoiceItem repairInvoiceItem;
- private final InvoiceItem reparationInvoiceItem;
-
- private AdjustmentInvoiceItemForRepair(final InvoiceItem repairInvoiceItem,
- final InvoiceItem reparationInvoiceItem) {
- this.repairInvoiceItem = repairInvoiceItem;
- this.reparationInvoiceItem = reparationInvoiceItem;
- }
-
- @Override
- public InvoiceItemType getInvoiceItemType() {
- return InvoiceItemType.ITEM_ADJ;
- }
-
- @Override
- public UUID getInvoiceId() {
- return repairInvoiceItem.getInvoiceId();
- }
-
- @Override
- public UUID getAccountId() {
- return repairInvoiceItem.getAccountId();
- }
-
- @Override
- public LocalDate getStartDate() {
- return repairInvoiceItem.getStartDate();
- }
-
- @Override
- public LocalDate getEndDate() {
- return repairInvoiceItem.getStartDate();
- }
-
- @Override
- public BigDecimal getAmount() {
- return reparationInvoiceItem.getAmount().add(repairInvoiceItem.getAmount());
- }
-
- @Override
- public Currency getCurrency() {
- return repairInvoiceItem.getCurrency();
- }
-
- @Override
- public String getDescription() {
- return null;
- }
-
- @Override
- public UUID getBundleId() {
- return null;
- }
-
- @Override
- public UUID getSubscriptionId() {
- return null;
- }
-
- @Override
- public String getPlanName() {
- return null;
- }
-
- @Override
- public String getPhaseName() {
- return null;
- }
-
- @Override
- public BigDecimal getRate() {
- return null;
- }
-
- @Override
- public UUID getLinkedItemId() {
- return repairInvoiceItem.getLinkedItemId();
- }
-
- @Override
- public boolean matches(final Object other) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public UUID getId() {
- // We pretend to be the repair, the reparation item record id
- // will be available as secondId
- return repairInvoiceItem.getId();
- }
-
- public UUID getSecondId() {
- return reparationInvoiceItem.getId();
- }
-
- @Override
- public DateTime getCreatedDate() {
- return repairInvoiceItem.getCreatedDate();
- }
-
- @Override
- public DateTime getUpdatedDate() {
- return repairInvoiceItem.getUpdatedDate();
- }
+ return newCbasToAdd;
}
}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java
index 40ef30b..47c5259 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/utils/BusinessInvoiceUtils.java
@@ -28,8 +28,21 @@ import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBase
import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoiceItemBaseModelDao.BusinessInvoiceItemType;
import com.ning.billing.osgi.bundles.analytics.dao.model.BusinessInvoicePaymentBaseModelDao;
+/**
+ * Utilities to manipulate invoice and invoice items.
+ */
public class BusinessInvoiceUtils {
+ public static boolean isRepareeItemForRepairedItem(final InvoiceItem repairedInvoiceItem, final InvoiceItem invoiceItem) {
+ return repairedInvoiceItem.getInvoiceItemType().equals(invoiceItem.getInvoiceItemType()) &&
+ repairedInvoiceItem.getSubscriptionId().equals(invoiceItem.getSubscriptionId()) &&
+ repairedInvoiceItem.getStartDate().compareTo(invoiceItem.getStartDate()) == 0 &&
+ // FIXED items have a null end date
+ ((repairedInvoiceItem.getEndDate() == null && invoiceItem.getEndDate() == null) ||
+ (repairedInvoiceItem.getEndDate() != null && invoiceItem.getEndDate() != null && !repairedInvoiceItem.getEndDate().isBefore(invoiceItem.getEndDate()))) &&
+ !repairedInvoiceItem.getId().equals(invoiceItem.getId());
+ }
+
public static Boolean isRevenueRecognizable(final InvoiceItem invoiceItem, final Collection<InvoiceItem> otherInvoiceItems) {
// All items are recognizable except user generated credit (CBA_ADJ and CREDIT_ADJ on their own invoice)
return !(InvoiceItemType.CBA_ADJ.equals(invoiceItem.getInvoiceItemType()) &&
@@ -69,8 +82,8 @@ public class BusinessInvoiceUtils {
InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType());
}
- public static BigDecimal computeInvoiceBalance(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
- @Nullable final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
+ public static BigDecimal computeInvoiceBalance(@Nullable final Iterable<BusinessInvoiceItemBaseModelDao> businessInvoiceItems,
+ @Nullable final Iterable<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
return computeInvoiceAmountCharged(businessInvoiceItems)
.add(computeInvoiceAmountCredited(businessInvoiceItems))
.add(
@@ -79,7 +92,7 @@ public class BusinessInvoiceUtils {
);
}
- public static BigDecimal computeInvoiceAmountCharged(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
+ public static BigDecimal computeInvoiceAmountCharged(@Nullable final Iterable<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
BigDecimal amountCharged = BigDecimal.ZERO;
if (businessInvoiceItems == null) {
return amountCharged;
@@ -95,7 +108,7 @@ public class BusinessInvoiceUtils {
return amountCharged;
}
- public static BigDecimal computeInvoiceOriginalAmountCharged(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
+ public static BigDecimal computeInvoiceOriginalAmountCharged(@Nullable final Iterable<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
BigDecimal amountCharged = BigDecimal.ZERO;
if (businessInvoiceItems == null) {
return amountCharged;
@@ -110,7 +123,7 @@ public class BusinessInvoiceUtils {
return amountCharged;
}
- public static BigDecimal computeInvoiceAmountCredited(@Nullable final Collection<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
+ public static BigDecimal computeInvoiceAmountCredited(@Nullable final Iterable<BusinessInvoiceItemBaseModelDao> businessInvoiceItems) {
BigDecimal amountCredited = BigDecimal.ZERO;
if (businessInvoiceItems == null) {
return amountCredited;
@@ -124,7 +137,7 @@ public class BusinessInvoiceUtils {
return amountCredited;
}
- public static BigDecimal computeInvoiceAmountPaid(@Nullable final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
+ public static BigDecimal computeInvoiceAmountPaid(@Nullable final Iterable<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
BigDecimal amountPaid = BigDecimal.ZERO;
if (businessInvoicePayments == null) {
return amountPaid;
@@ -138,7 +151,7 @@ public class BusinessInvoiceUtils {
return amountPaid;
}
- public static BigDecimal computeInvoiceAmountRefunded(@Nullable final Collection<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
+ public static BigDecimal computeInvoiceAmountRefunded(@Nullable final Iterable<BusinessInvoicePaymentBaseModelDao> businessInvoicePayments) {
BigDecimal amountRefunded = BigDecimal.ZERO;
if (businessInvoicePayments == null) {
return amountRefunded;
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
index b13f3a7..65e7e58 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/TestBusinessInvoiceAndInvoicePaymentDao.java
@@ -18,7 +18,6 @@ package com.ning.billing.osgi.bundles.analytics.dao;
import java.math.BigDecimal;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -45,7 +44,9 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillAPI;
import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
public class TestBusinessInvoiceAndInvoicePaymentDao extends AnalyticsTestSuiteNoDB {
@@ -139,8 +140,8 @@ public class TestBusinessInvoiceAndInvoicePaymentDao extends AnalyticsTestSuiteN
// Compute the pojos
final Map<UUID, BusinessInvoiceModelDao> invoices = new HashMap<UUID, BusinessInvoiceModelDao>();
- final Map<UUID, Collection<BusinessInvoiceItemBaseModelDao>> invoiceItems = new HashMap<UUID, Collection<BusinessInvoiceItemBaseModelDao>>();
- final Map<UUID, Collection<BusinessInvoicePaymentBaseModelDao>> invoicePayments = new HashMap<UUID, Collection<BusinessInvoicePaymentBaseModelDao>>();
+ final Multimap<UUID, BusinessInvoiceItemBaseModelDao> invoiceItems = ArrayListMultimap.<UUID, BusinessInvoiceItemBaseModelDao>create();
+ final Multimap<UUID, BusinessInvoicePaymentBaseModelDao> invoicePayments = ArrayListMultimap.<UUID, BusinessInvoicePaymentBaseModelDao>create();
dao.createBusinessPojos(account.getId(), invoices, invoiceItems, invoicePayments, callContext);
/*