diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java index a74bd6ede2..cf17f4d2d0 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java @@ -195,7 +195,7 @@ public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotif final List allUsage = getRolledUpUsage(); for (final RolledUpUsage ru : allUsage) { - int tierNum = 1; + List toBeBilledUsageDetails = Lists.newLinkedList(); BigDecimal toBeBilledUsage = BigDecimal.ZERO; if (usage.getUsageType() == UsageType.CAPACITY) { @@ -209,7 +209,7 @@ public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotif continue; } - toBeBilledUsageDetails.addAll(computeToBeBilledConsumableInArrear(cur, tierNum++)); + toBeBilledUsageDetails.addAll(computeToBeBilledConsumableInArrear(cur)); } } @@ -424,28 +424,31 @@ List computeToBeBilledCapacityInArrear(final List computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit, int tierNum) throws CatalogApiException { + List computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit) throws CatalogApiException { Preconditions.checkState(isBuilt.get()); - final List tieredBlocks = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType()); + final List> tieredBlocksWithTierNum = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType()); switch (usage.getTierBlockPolicy()) { case ALL_TIERS: - return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, roUnit.getAmount(), tierNum); + return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocksWithTierNum, roUnit.getAmount()); case TOP_TIER: - return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, roUnit.getAmount(), tierNum)); + return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocksWithTierNum, roUnit.getAmount())); default: throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy()); } } - List computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List tieredBlocks, final Long units, int tierNum) throws CatalogApiException { + List computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List> tieredBlocksWithTierNum, final Long units) throws CatalogApiException { List toBeBilledDetails = Lists.newLinkedList(); BigDecimal result = BigDecimal.ZERO; int remainingUnits = units.intValue(); - for (final TieredBlock tieredBlock : tieredBlocks) { + for (final Map tieredBlockWithTierNum : tieredBlocksWithTierNum) { + + final TieredBlock tieredBlock = tieredBlockWithTierNum.entrySet().iterator().next().getValue(); + final int tierNum = tieredBlockWithTierNum.entrySet().iterator().next().getKey(); final int blockTierSize = tieredBlock.getSize().intValue(); final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1); @@ -457,19 +460,27 @@ List computeToBeBilledConsumableInArrearWith_ALL_TIERS(fina nbUsedTierBlocks = tmp; remainingUnits = 0; } - toBeBilledDetails.add(new UsageInArrearDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), nbUsedTierBlocks)); + + if (nbUsedTierBlocks > 0) { + toBeBilledDetails.add(new UsageInArrearDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), nbUsedTierBlocks)); + } } return toBeBilledDetails; } - UsageInArrearDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List tieredBlocks, final Long units, int tierNum) throws CatalogApiException { + UsageInArrearDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List> tieredBlocksWithTierNum, final Long units) throws CatalogApiException { int remainingUnits = units.intValue(); // By default last last tierBlock - TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1); + Map targetBlockWithTierNum = tieredBlocksWithTierNum.get(tieredBlocksWithTierNum.size() - 1); + TieredBlock targetBlock = targetBlockWithTierNum.entrySet().iterator().next().getValue(); + int targetTierNum = targetBlockWithTierNum.entrySet().iterator().next().getKey(); // Loop through all tier block - for (final TieredBlock tieredBlock : tieredBlocks) { + for (final Map tieredBlockWithTierNum : tieredBlocksWithTierNum) { + + final TieredBlock tieredBlock = tieredBlockWithTierNum.entrySet().iterator().next().getValue(); + final int tierNum = tieredBlockWithTierNum.entrySet().iterator().next().getKey(); final int blockTierSize = tieredBlock.getSize().intValue(); final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1); @@ -477,13 +488,14 @@ UsageInArrearDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List< remainingUnits -= tieredBlock.getMax() * blockTierSize; } else { targetBlock = tieredBlock; + targetTierNum = tierNum; break; } } final int lastBlockTierSize = targetBlock.getSize().intValue(); final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1); - return new UsageInArrearDetail(tierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), nbBlocks); + return new UsageInArrearDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), nbBlocks); } diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java index 0a39859cd9..0293bd6ee1 100644 --- a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java +++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java @@ -16,8 +16,10 @@ package org.killbill.billing.invoice.usage; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.killbill.billing.catalog.api.BillingMode; @@ -33,16 +35,20 @@ public class UsageUtils { - public static List getConsumableInArrearTieredBlocks(final Usage usage, final String unitType) { + public static List> getConsumableInArrearTieredBlocks(final Usage usage, final String unitType) { Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CONSUMABLE); Preconditions.checkArgument(usage.getTiers().length > 0); - final List result = Lists.newLinkedList(); + final List> result = Lists.newLinkedList(); + int tierNum = 0; for (Tier tier : usage.getTiers()) { + tierNum++; for (TieredBlock tierBlock : tier.getTieredBlocks()) { if (tierBlock.getUnit().getName().equals(unitType)) { - result.add(tierBlock); + Map tieredBlockWithTierNum = new HashMap<>(); + tieredBlockWithTierNum.put(tierNum, tierBlock); + result.add(tieredBlockWithTierNum); } } } 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 1483fa4d24..00b825d052 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 @@ -145,7 +145,7 @@ public void testComputeBilledUsageSizeOneWith_ALL_TIERS() throws CatalogApiExcep Collections.emptyList()) ); - List result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), 1); + List result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L)); assertEquals(result.size(), 3); // 111 = 10 (tier1) + 100 (tier2) + 1 (tier3) => 10 * 1.5 + 100 * 1 + 1 * 0.5 = 115.5 assertEquals(result.get(0).getAmount(), new BigDecimal("15.0")); @@ -173,7 +173,7 @@ public void testComputeBilledUsageWith_ALL_TIERS() throws CatalogApiException { Collections.emptyList()) ); - List result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L), 1); + List result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L)); assertEquals(result.size(), 2); // 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15 @@ -207,23 +207,23 @@ public void testComputeBilledUsageWith_TOP_TIER() throws CatalogApiException { // // In this model unit amount is first used to figure out which tier we are in, and then we price all unit at that 'target' tier // - List inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L), 1); + List inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L)); assertEquals(inputTier1.size(), 1); // 1000 units => (tier1) : 1000 / 100 + 1000 % 100 = 10 assertEquals(inputTier1.get(0).getAmount(), new BigDecimal("10")); - List inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L), 1); + List inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L)); assertEquals(inputTier2.size(), 1); // 101000 units => (tier2) : 101000 / 1000 + 101000 % 1000 = 101 + 0 = 101 assertEquals(inputTier2.get(0).getAmount(), new BigDecimal("101")); - List inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L), 1); + List inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L)); assertEquals(inputTier3.size(), 1); // 101001 units => (tier3) : 101001 / 1000 + 101001 % 1000 = 101 + 1 = 102 units => $51 assertEquals(inputTier3.get(0).getAmount(), new BigDecimal("51.0")); // If we pass the maximum of the last tier, we price all units at the last tier - List inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L), 1); + List inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L)); assertEquals(inputLastTier.size(), 1); // 300000 units => (tier3) : 300000 / 1000 + 300000 % 1000 = 300 units => $150 assertEquals(inputLastTier.get(0).getAmount(), new BigDecimal("150.0")); @@ -252,7 +252,7 @@ public void testComputeBilledUsageSizeOneWith_TOP_TIER() throws CatalogApiExcept Collections.emptyList()) ); - List result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), 1); + List result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L)); assertEquals(result.size(), 1); // 111 = 111 * 0.5 = @@ -462,8 +462,8 @@ public void testTobeBilledForUnit() throws CatalogApiException { Collections.emptyList()) ); List results = Lists.newArrayList(); - results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L), 1)); - results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L), 2)); + results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L))); + results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L))); assertEquals(results.size(), 2); assertEquals(intervalConsumableInArrear.toBeBilledForUnit(results), new BigDecimal("18.5"));