killbill-memoizeit

invoice: Fix issue in consumable in arrear (DETAIL) mode when

2/15/2018 12:13:37 AM

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
index 7e0d45a..ee0e65b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
@@ -30,6 +30,8 @@ import java.util.UUID;
 import org.joda.time.LocalDate;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Tier;
 import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.TieredBlock;
 import org.killbill.billing.catalog.api.Usage;
@@ -79,7 +81,7 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
             if (UsageDetailMode.DETAIL == usageDetailMode) {
                 for (UsageConsumableInArrearTierUnitDetail toBeBilledUsageDetail : ((UsageConsumableInArrearDetail) toBeBilledUsageDetails).getTierDetails()) {
                     final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
-                                                                  getPhaseName(), usage.getName(), startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), null);
+                                                                  getPhaseName(), usage.getName(), startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), toBeBilledUsageDetail.getTierUnit());
                     result.add(item);
                 }
             } else {
@@ -95,7 +97,7 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
     protected UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
 
         final Map<String, List<UsageConsumableInArrearTierUnitDetail>> previousUnitsUsage;
-        if (areAllBilledItemsWithDetails) {
+        if (usageDetailMode == UsageDetailMode.DETAIL || areAllBilledItemsWithDetails) {
             previousUnitsUsage = new HashMap<String, List<UsageConsumableInArrearTierUnitDetail>>();
             for (RolledUpUnit cur : rolledUpUnits) {
                 final List<UsageConsumableInArrearTierUnitDetail> usageInArrearDetailForUnitType = getBilledDetailsForUnitType(billedItems, cur.getUnitType());
@@ -120,24 +122,66 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
         return toBeBilledUsageDetails;
     }
 
+
     @VisibleForTesting
     List<UsageConsumableInArrearTierUnitDetail> getBilledDetailsForUnitType(final Iterable<InvoiceItem> billedItems, final String unitType) {
 
         // Aggregate on a per-tier level, will return a list with item per level -- for this 'unitType'
         final Map<Integer, UsageConsumableInArrearTierUnitDetail> resultMap = new TreeMap<Integer, UsageConsumableInArrearTierUnitDetail>(Ordering.<Integer>natural());
+
+
+
+        List<UsageConsumableInArrearTierUnitDetail> tierDetails = new ArrayList<UsageConsumableInArrearTierUnitDetail>();
         for (final InvoiceItem bi : billedItems) {
 
-            final UsageConsumableInArrearDetail usageDetail = fromJson(bi.getItemDetails());
-            for (final UsageConsumableInArrearTierUnitDetail curDetail : usageDetail.getTierDetails()) {
+            if (usageDetailMode == UsageDetailMode.DETAIL) {
 
-                if (curDetail.getTierUnit().equals(unitType)) {
+                final Currency currency = getCurrency();
 
-                    if (!resultMap.containsKey(curDetail.getTier())) {
-                        resultMap.put(curDetail.getTier(), curDetail);
-                    } else {
-                        final UsageConsumableInArrearTierUnitDetail perTierDetail = resultMap.get(curDetail.getTier());
-                        perTierDetail.updateQuantityAndAmount(curDetail.getQuantity());
+                final String biUnitType = bi.getItemDetails();
+                if (!biUnitType.equals(unitType)) {
+                    continue;
+                }
+
+                int tierLevel = 0;
+                TieredBlock targetTier = null;
+                for (Tier tier : usage.getTiers()) {
+                    tierLevel++;
+                    for (TieredBlock tierBlock : tier.getTieredBlocks()) {
+                        if (tierBlock.getUnit().getName().equals(unitType)) {
+                            try {
+                                if (tierBlock.getPrice().getPrice(currency).compareTo(bi.getRate()) == 0) {
+                                    targetTier = tierBlock;
+                                    break;
+                                }
+                            } catch (CatalogApiException e) {
+                                throw new IllegalStateException(String.format("Failed to extract catalog price for currency '%s'", currency), e);
+                            }
+                        }
                     }
+                    if (targetTier != null) {
+                        break;
+                    }
+                }
+
+                Preconditions.checkState(targetTier != null, "OHHHHHH some is wrong!!!");
+
+                tierDetails.add(new UsageConsumableInArrearTierUnitDetail(tierLevel, biUnitType, bi.getRate(), targetTier.getSize().intValue(), bi.getQuantity(), bi.getAmount()));
+            } else {
+                final UsageConsumableInArrearDetail usageDetail = fromJson(bi.getItemDetails());
+                tierDetails.addAll(usageDetail.getTierDetails());
+            }
+        }
+
+        for (final UsageConsumableInArrearTierUnitDetail curDetail : tierDetails) {
+
+            if (curDetail.getTierUnit().equals(unitType)) {
+
+                if (!resultMap.containsKey(curDetail.getTier())) {
+                    resultMap.put(curDetail.getTier(), curDetail);
+                } else {
+                    final UsageConsumableInArrearTierUnitDetail perTierDetail = resultMap.get(curDetail.getTier());
+                    perTierDetail.updateQuantityAndAmount(curDetail.getQuantity());
                 }
             }
         }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index 7fd4e71..6a07028 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -518,7 +518,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsAggregateModeAllTier() throws CatalogApiException, IOException, InvoiceApiException {
+    public void testComputeMissingItemsAggregateModeAllTier_AGGREGATE() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
@@ -620,7 +620,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsDetailModeAllTier() throws CatalogApiException, IOException, InvoiceApiException {
+    public void testComputeMissingItemsDetailModeAllTier_DETAIL() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
@@ -688,7 +688,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsAggregateModeTopTier() throws CatalogApiException, IOException, InvoiceApiException {
+    public void testComputeMissingItemsAggregateModeTopTier_AGGREGATE() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
@@ -765,7 +765,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsDetailModeTopTier() throws CatalogApiException, IOException, InvoiceApiException {
+    public void testComputeMissingItemsDetailModeTopTier_DETAIL() throws CatalogApiException, IOException, InvoiceApiException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
@@ -819,7 +819,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
 
     @Test(groups = "fast")
-    public void testMultipleItemsAndTiersWithExistingItemsAllTiers() throws CatalogApiException, IOException, InvoiceApiException {
+    public void testMultipleItemsAndTiersWithExistingItemsAllTiers_AGGREGATE() throws CatalogApiException, IOException, InvoiceApiException {
 
         //
         // Let's assume we were already billed on the previous period
@@ -854,6 +854,9 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
         List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
 
+
+        // We get same total than AGGREGATE : 3140
+
         // BAR item detail
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -887,6 +890,55 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
 
     @Test(groups = "fast")
+    public void testMultipleItemsAndTiersWithExistingItemsAllTiers_DETAIL() throws CatalogApiException, IOException, InvoiceApiException {
+
+
+        //
+        // Create usage data points (will include already billed + add new usage data)
+        //
+        List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 50L /* already built */ + 20L)); // tier 3
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L)); // tier 2
+
+        // Same as previous example bu instead of creating JSON we create one item per type/tier
+        final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+        final InvoiceItem i1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("10.00") /* amount */,  new BigDecimal("1.00") /* rate = tierPrice*/, currency, 10 /* # units*/, "FOO");
+        final InvoiceItem i2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("400.00"),  new BigDecimal("10.00"), currency, 40, "FOO");
+        final InvoiceItem i3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("160.00"),  new BigDecimal("2.00"), currency, 80, "BAR");
+        existingItems.addAll(ImmutableList.<InvoiceItem>of(i1, i2, i3));
+
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, existingItems);
+        assertEquals(result.size(), 4);
+        assertEquals(result.get(0).getItemDetails(), "BAR");
+        assertEquals(result.get(0).getRate().compareTo(new BigDecimal("2.00")), 0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("40.00")), 0);
+        assertEquals(result.get(0).getQuantity().intValue(), 20);
+
+        assertEquals(result.get(1).getItemDetails(), "BAR");
+        assertEquals(result.get(1).getRate().compareTo(new BigDecimal("20.00")), 0);
+        assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("2000.00")), 0);
+        assertEquals(result.get(1).getQuantity().intValue(), 100);
+
+        assertEquals(result.get(2).getItemDetails(), "FOO");
+        assertEquals(result.get(2).getRate().compareTo(new BigDecimal("10.00")), 0);
+        assertEquals(result.get(2).getAmount().compareTo(new BigDecimal("100.00")), 0);
+        assertEquals(result.get(2).getQuantity().intValue(), 10);
+
+        assertEquals(result.get(3).getItemDetails(), "FOO");
+        assertEquals(result.get(3).getRate().compareTo(new BigDecimal("100.00")), 0);
+        assertEquals(result.get(3).getAmount().compareTo(new BigDecimal("1000.00")), 0);
+        assertEquals(result.get(3).getQuantity().intValue(), 10);
+
+    }
+
+
+
+
+
+
+
+
+    @Test(groups = "fast")
     public void testMultipleItemsAndTiersWithExistingItemsTopTier() throws CatalogApiException, IOException {
         // TODO + code
     }