killbill-memoizeit

Details

diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
index 2841609..bc75ed7 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
@@ -23,6 +23,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
 
 import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
@@ -41,6 +42,10 @@ import org.killbill.billing.util.config.catalog.ValidationErrors;
 public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements Usage {
 
     @XmlAttribute(required = true)
+    @XmlID
+    private String name;
+
+    @XmlAttribute(required = true)
     private BillingMode billingMode;
 
     @XmlAttribute(required = true)
@@ -77,6 +82,11 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
     private PlanPhase phase;
 
     @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
     public BillingMode getBillingMode() {
         return billingMode;
     }
@@ -171,6 +181,11 @@ public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements
         return this;
     }
 
+    public DefaultUsage setName(final String name) {
+        this.name = name;
+        return this;
+    }
+
     public DefaultUsage setBillingMode(final BillingMode billingMode) {
         this.billingMode = billingMode;
         return this;
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java
index 64cccf7..f05c686 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java
@@ -67,6 +67,7 @@ public class TestXMLReader extends CatalogTestSuiteNoDB {
             assertEquals(usages.length, 1);
             final Usage usage = usages[0];
 
+            assertEquals(usage.getName(), "capacity-in-advance-monthly-usage1");
             assertEquals(usage.getBillingPeriod(), BillingPeriod.MONTHLY);
             assertEquals(usage.getUsageType(), UsageType.CAPACITY);
             assertEquals(usage.getBillingMode(), BillingMode.IN_ADVANCE);
@@ -96,6 +97,7 @@ public class TestXMLReader extends CatalogTestSuiteNoDB {
             assertEquals(usages.length, 1);
             final Usage usage = usages[0];
 
+            assertEquals(usage.getName(), "consumable-in-advance-prepay-credit-monthly-usage1");
             assertEquals(usage.getBillingPeriod(), BillingPeriod.MONTHLY);
             assertEquals(usage.getUsageType(), UsageType.CONSUMABLE);
             assertEquals(usage.getBillingMode(), BillingMode.IN_ADVANCE);
@@ -127,6 +129,7 @@ public class TestXMLReader extends CatalogTestSuiteNoDB {
             assertEquals(usages.length, 1);
             final Usage usage = usages[0];
 
+            assertEquals(usage.getName(), "consumable-in-advance-topup-usage1");
             assertEquals(usage.getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD);
             assertEquals(usage.getUsageType(), UsageType.CONSUMABLE);
             assertEquals(usage.getBillingMode(), BillingMode.IN_ADVANCE);
@@ -161,6 +164,7 @@ public class TestXMLReader extends CatalogTestSuiteNoDB {
             assertEquals(usages.length, 1);
             final Usage usage = usages[0];
 
+            assertEquals(usage.getName(), "capacity-in-arrear-usage1");
             assertEquals(usage.getBillingPeriod(), BillingPeriod.MONTHLY);
             assertEquals(usage.getUsageType(), UsageType.CAPACITY);
             assertEquals(usage.getBillingMode(), BillingMode.IN_ARREAR);
@@ -210,6 +214,7 @@ public class TestXMLReader extends CatalogTestSuiteNoDB {
             assertEquals(usages.length, 1);
             final Usage usage = usages[0];
 
+            assertEquals(usage.getName(), "consumable-in-arrear-usage1");
             assertEquals(usage.getBillingPeriod(), BillingPeriod.MONTHLY);
             assertEquals(usage.getUsageType(), UsageType.CONSUMABLE);
             assertEquals(usage.getBillingMode(), BillingMode.IN_ARREAR);
diff --git a/catalog/src/test/resources/UsageExperimental.xml b/catalog/src/test/resources/UsageExperimental.xml
index cd18fd9..11f3d17 100644
--- a/catalog/src/test/resources/UsageExperimental.xml
+++ b/catalog/src/test/resources/UsageExperimental.xml
@@ -74,7 +74,7 @@
                 </duration>
 
                 <usages>
-                    <usage billingMode="IN_ADVANCE" usageType="CAPACITY">
+                    <usage name="capacity-in-advance-monthly-usage1" billingMode="IN_ADVANCE" usageType="CAPACITY">
                         <billingPeriod>MONTHLY</billingPeriod>
                         <limits>
                             <limit>
@@ -102,7 +102,7 @@
                     <unit>UNLIMITED</unit>
                 </duration>
                 <usages>
-                    <usage billingMode="IN_ADVANCE" usageType="CONSUMABLE">
+                    <usage name="consumable-in-advance-prepay-credit-monthly-usage1" billingMode="IN_ADVANCE" usageType="CONSUMABLE">
                         <billingPeriod>MONTHLY</billingPeriod>
                         <blocks>
                             <block>
@@ -135,7 +135,7 @@
                 </duration>
 
                 <usages>
-                    <usage billingMode="IN_ADVANCE" usageType="CONSUMABLE">
+                    <usage name="consumable-in-advance-topup-usage1" billingMode="IN_ADVANCE" usageType="CONSUMABLE">
                         <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
                         <blocks>
                             <block type="TOP_UP">
@@ -164,7 +164,7 @@
                 </duration>
 
                 <usages>
-                    <usage billingMode="IN_ARREAR" usageType="CAPACITY">
+                    <usage name="capacity-in-arrear-usage1" billingMode="IN_ARREAR" usageType="CAPACITY">
                         <billingPeriod>MONTHLY</billingPeriod>
                         <tiers>
                             <tier>
@@ -229,7 +229,7 @@
                     <unit>UNLIMITED</unit>
                 </duration>
                 <usages>
-                    <usage billingMode="IN_ARREAR" usageType="CONSUMABLE">
+                    <usage name="consumable-in-arrear-usage1" billingMode="IN_ARREAR" usageType="CONSUMABLE">
                         <billingPeriod>MONTHLY</billingPeriod>
                         <tiers>
                             <tier>
diff --git a/catalog/src/test/resources/WeaponsHireSmall.xml b/catalog/src/test/resources/WeaponsHireSmall.xml
index 3a686dd..c0501d3 100644
--- a/catalog/src/test/resources/WeaponsHireSmall.xml
+++ b/catalog/src/test/resources/WeaponsHireSmall.xml
@@ -122,7 +122,7 @@
                     </recurringPrice>
                 </recurring>
                 <usages>
-                    <usage billingMode="IN_ADVANCE" usageType="CAPACITY">
+                    <usage name="usage1" billingMode="IN_ADVANCE" usageType="CAPACITY">
                         <billingPeriod>MONTHLY</billingPeriod>
                         <limits>
                             <limit>
@@ -226,7 +226,7 @@
                     </recurringPrice>
                 </recurring>
                 <usages>
-                    <usage billingMode="IN_ADVANCE" usageType="CAPACITY">
+                    <usage name="usage2" billingMode="IN_ADVANCE" usageType="CAPACITY">
                         <billingPeriod>ANNUAL</billingPeriod>
                         <limits>
                             <limit>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 3a57387..66af2e6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -28,6 +28,7 @@ import javax.annotation.Nullable;
 import org.joda.time.LocalDate;
 import org.joda.time.Months;
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
@@ -51,6 +52,7 @@ import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.util.config.InvoiceConfig;
 import org.killbill.billing.util.currency.KillBillMoney;
+import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -69,12 +71,14 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     private final Clock clock;
     private final InvoiceConfig config;
     private final UsageUserApi usageApi;
+    private final NonEntityDao nonEntityDao;
 
     @Inject
-    public DefaultInvoiceGenerator(final Clock clock, final UsageUserApi usageApi, final InvoiceConfig config) {
+    public DefaultInvoiceGenerator(final Clock clock, final UsageUserApi usageApi, final InvoiceConfig config, final NonEntityDao nonEntityDao) {
         this.clock = clock;
         this.config = config;
         this.usageApi = usageApi;
+        this.nonEntityDao = nonEntityDao;
     }
 
     /*
@@ -109,6 +113,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                                                         @Nullable final List<Invoice> existingInvoices, final LocalDate targetDate,
                                                         final InternalCallContext context) throws InvoiceApiException {
 
+        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
         try {
 
             final List<InvoiceItem> items = Lists.newArrayList();
@@ -120,18 +125,13 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                 final BillingEvent event = events.next();
                 final UUID subscriptionId = event.getSubscription().getId();
                 if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) {
-                    //
-                    // STEPH_USAGE unitType issue
-                    // STEPH_USAGE context needs tenantId , hum...
-                    final UUID tenantId = UUID.randomUUID();
-                    SubscriptionConsumableInArrear foo = new SubscriptionConsumableInArrear(invoiceId, "foo", curEvents, usageApi, targetDate, context.toTenantContext(tenantId));
-                    items.addAll(foo.computeMissingUsageInvoiceItems(extractUsageItemsForSubscription(subscriptionId, existingInvoices)));
+                    final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, usageApi, targetDate, context.toTenantContext(tenantId));
+                    items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(extractUsageItemsForSubscription(subscriptionId, existingInvoices)));
                     curEvents.clear();
                 }
                 curSubscriptionId = subscriptionId;
                 curEvents.add(event);
             }
-
             return items;
 
         } catch (CatalogApiException e) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
index de3bf99..a75c782 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
@@ -79,7 +79,7 @@ public class InvoiceItemFactory {
                 item = new ItemAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, amount, currency, linkedItemId);
                 break;
             case USAGE:
-                // STEPH USAGE should we add unitType in schema pr override fields (planName,..) Same for unitAmount
+                // STEPH_USAGE should we add unitType in schema or override fields (planName,..) Same for unitAmount
                 item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency,"unitType");
                 break;
             default:
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
index 9b6489e..f9d8892 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
@@ -28,19 +28,19 @@ import org.killbill.billing.invoice.api.InvoiceItemType;
 
 public class UsageInvoiceItem extends InvoiceItemBase {
 
-    private final String unitType;
+    private final String usageName;
 
     public UsageInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
                             final String planName, final String phaseName,
-                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, final String unitType) {
-        this(UUID.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, unitType);
+                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, final String usageName) {
+        this(UUID.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, usageName);
     }
 
     public UsageInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
                             final UUID subscriptionId, final String planName, final String phaseName,
-                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, final String unitType) {
+                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, final String usageName) {
         super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
-        this.unitType = unitType;
+        this.usageName = usageName;
     }
 
     @Override
@@ -50,10 +50,10 @@ public class UsageInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s (usage item)", unitType);
+        return String.format("%s (usage item)", usageName);
     }
 
-    public String getUnitType() {
-        return unitType;
+    public String getUsageName() {
+        return usageName;
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousInArrearUsageInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousInArrearUsageInterval.java
index 405306e..b05f6d9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousInArrearUsageInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousInArrearUsageInterval.java
@@ -17,7 +17,12 @@
 package org.killbill.billing.invoice.usage;
 
 import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -44,24 +49,29 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 import static org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.getTieredBlocks;
+import static org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.getUnitTypes;
 import static org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear.localDateToEndOfDayInAccountTimezone;
 
+/**
+ * There is one such class per subscriptionId, matching a given in arrear/consumable usage section and
+ * referenced through a contiguous list of billing events.
+ */
 public class ContiguousInArrearUsageInterval {
 
     private final List<LocalDate> transitionTimes;
     private final List<BillingEvent> billingEvents;
 
     private final Usage usage;
-    private final String unitType;
+    private final Set<String> unitTypes;
     private final UsageUserApi usageApi;
     private final LocalDate targetDate;
     private final UUID invoiceId;
     private final TenantContext context;
 
-    public ContiguousInArrearUsageInterval(final Usage usage, final UUID invoiceId, final String unitType, final UsageUserApi usageApi, final LocalDate targetDate, final TenantContext context) {
+    public ContiguousInArrearUsageInterval(final Usage usage, final UUID invoiceId, final UsageUserApi usageApi, final LocalDate targetDate, final TenantContext context) {
         this.usage = usage;
         this.invoiceId = invoiceId;
-        this.unitType = unitType;
+        this.unitTypes = getUnitTypes(usage);
         this.usageApi = usageApi;
         this.targetDate = targetDate;
         this.context = context;
@@ -69,49 +79,16 @@ public class ContiguousInArrearUsageInterval {
         this.transitionTimes = Lists.newLinkedList();
     }
 
-    public void addBillingEvent(final BillingEvent event) {
-        billingEvents.add(event);
-    }
-
-    public Usage getUsage() {
-        return usage;
-    }
-
-    public int getBCD() {
-        return billingEvents.get(0).getBillCycleDayLocal();
-    }
-
-    public UUID getAccountId() {
-        return billingEvents.get(0).getAccount().getId();
-    }
-
-    public UUID getBundleId() {
-        return billingEvents.get(0).getSubscription().getBundleId();
-    }
-
-    public UUID getSubscriptionId() {
-        return billingEvents.get(0).getSubscription().getId();
-    }
-
-    // STEPH_USAGE planName/phaseName,BCD,... might not be correct if we changed plan but Usage section was exactly similar
-    public String getPlanName() {
-        return billingEvents.get(0).getPlan().getName();
-    }
-
-    public String getPhaseName() {
-        return billingEvents.get(0).getPlanPhase().getName();
-    }
-
-    public Currency getCurrency() {
-        return billingEvents.get(0).getCurrency();
-    }
-
-    public DateTimeZone getAccountTimeZone() {
-        return billingEvents.get(0).getTimeZone();
-    }
-
-
-
+    /**
+     * Builds the transitionTimes associated to that usage section. Those are determined based on billing events for when to start and when to stop,
+     * the per usage billingPeriod and finally the targetDate.
+     * <p/>
+     * Those transition dates define the well defined billing granularity periods that should be billed for that specific usage section.
+     *
+     * @param closedInterval whether there was a last billing event referencing the usage section or whether this is ongoing and
+     *                       then targetDate will define the endDate.
+     * @return
+     */
     public ContiguousInArrearUsageInterval build(final boolean closedInterval) {
 
         Preconditions.checkState((!closedInterval && billingEvents.size() >= 1) ||
@@ -140,26 +117,38 @@ public class ContiguousInArrearUsageInterval {
         return this;
     }
 
+    /**
+     * Compute the missing usage invoice items based on what should be billed and what has been billed ($ amount comparison).
+     *
+     * @param existingUsage existing on disk usage items for the subscription
+     * @return
+     * @throws CatalogApiException
+     */
     public List<InvoiceItem> computeMissingItems(final List<InvoiceItem> existingUsage) throws CatalogApiException {
 
         final List<InvoiceItem> result = Lists.newLinkedList();
 
-        final List<RolledUpUsage> rolledUpUsages = getRolledUpUsage();
-        for (RolledUpUsage ru : rolledUpUsages) {
-            final LocalDate startRolledUpDate = new LocalDate(ru.getStartTime(), getAccountTimeZone());
-            final LocalDate endRolledUpDate = new LocalDate(ru.getEndTime(), getAccountTimeZone());
-            final BigDecimal billedUsage = computeBilledUsage(startRolledUpDate, endRolledUpDate, existingUsage);
-            final BigDecimal toBeBilledUsage = computeToBeBilledUsage(ru.getAmount());
-            if (billedUsage.compareTo(toBeBilledUsage) < 0) {
-                InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(),
-                                                        getPhaseName(), startRolledUpDate, endRolledUpDate, toBeBilledUsage.subtract(billedUsage), getCurrency(), unitType);
-                result.add(item);
+        final RolledUpUsageForUnitTypesFactory factory = new RolledUpUsageForUnitTypesFactory(getRolledUpUsage(), unitTypes, getAccountTimeZone());
+        for (RolledUpUsageForUnitTypes ru : factory.getOrderedRolledUpUsageForUnitTypes()) {
+
+            final BigDecimal billedUsage = computeBilledUsage(ru.getStartDate(), ru.getEndDate(), existingUsage);
+            final BigDecimal toBeBilledUsage = BigDecimal.ZERO;
+            for (final String unitType : unitTypes) {
+                final BigDecimal usageAmountForUnitType = ru.getUsageAmountForUnitType(unitType);
+                final BigDecimal toBeBilledForUnit = computeToBeBilledUsage(usageAmountForUnitType, unitType);
+                toBeBilledUsage.add(toBeBilledForUnit);
+
+                if (billedUsage.compareTo(toBeBilledUsage) < 0) {
+                    InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(),
+                                                            getPhaseName(), ru.getStartDate(), ru.getEndDate(), toBeBilledUsage.subtract(billedUsage), getCurrency(), usage.getName());
+                    result.add(item);
+                }
             }
         }
         return result;
     }
 
-    private List<RolledUpUsage> getRolledUpUsage() {
+    List<RolledUpUsage> getRolledUpUsage() {
 
         final Iterable<DateTime> transitions = Iterables.transform(transitionTimes, new Function<LocalDate, DateTime>() {
             @Override
@@ -167,36 +156,46 @@ public class ContiguousInArrearUsageInterval {
                 return localDateToEndOfDayInAccountTimezone(input, getAccountTimeZone());
             }
         });
-        final List<RolledUpUsage> usagesForInterval = usageApi.getAllUsageForSubscription(getSubscriptionId(), unitType, ImmutableList.copyOf(transitions), context);
-        return usagesForInterval;
+        // STEPH_USAGE optimized api takes set of unitTypes -- for usage section-- and list of transitions date. Should we use dateTime or LocalDate?
+        return usageApi.getAllUsageForSubscription(getSubscriptionId(), unitTypes, ImmutableList.copyOf(transitions), context);
     }
 
-    private final BigDecimal computeToBeBilledUsage(final BigDecimal units) throws CatalogApiException {
+    BigDecimal computeToBeBilledUsage(final BigDecimal nbUnits, final String unitType) throws CatalogApiException {
 
-        // STEPH_USAGE need to review catalog xml which defines block tiers, ...
-        final int blockSize = 0x1000;
-        final int nbBlocks = units.intValue() / blockSize + ((units.intValue() % blockSize == 0) ? 0 : 1);
+        BigDecimal result = BigDecimal.ZERO;
 
-        // STEPH_USAGE this is wrong should use from each tier.
         final List<TieredBlock> tieredBlocks = getTieredBlocks(usage, unitType);
-        for (TieredBlock tier : tieredBlocks) {
-            if (tier.getMax() >= units.doubleValue()) {
-                return tier.getPrice().getPrice(getCurrency());
+        int remainingUnits = nbUnits.intValue();
+        for (TieredBlock tieredBlock : tieredBlocks) {
+
+            final int blockTierSize = tieredBlock.getSize().intValue();
+            final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
+            final int nbUsedTierBlocks;
+            if (tmp > tieredBlock.getMax()) {
+                nbUsedTierBlocks = tieredBlock.getMax().intValue();
+                remainingUnits -= tieredBlock.getMax() * blockTierSize;
+            } else {
+                nbUsedTierBlocks = tmp;
+                remainingUnits = 0;
             }
+            result.add(tieredBlock.getPrice().getPrice(getCurrency()).multiply(new BigDecimal(nbUsedTierBlocks)));
         }
-        // Return from last tier
-        return tieredBlocks.get(tieredBlocks.size() - 1).getPrice().getPrice(getCurrency());
+        return result;
     }
 
-    private final BigDecimal computeBilledUsage(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
+    BigDecimal computeBilledUsage(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
         final Iterable<InvoiceItem> filteredUsageForInterval = Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
             @Override
             public boolean apply(final InvoiceItem input) {
+                if (input.getInvoiceItemType() != InvoiceItemType.USAGE) {
+                    return false;
+                }
+
                 // STEPH_USAGE what happens if we discover usage period that overlap (one side or both side) the [startDate, endDate] interval
-                // STEPH_USAGE how to distinguish different usage charges (maybe different sections.) (needs to at least of the unitType in usage element
-                return input.getInvoiceItemType() == InvoiceItemType.USAGE &&
-                       input.getStartDate().compareTo(startDate) >= 0 &&
-                       input.getEndDate().compareTo(endDate) <= 0;
+                final UsageInvoiceItem usageInput = (UsageInvoiceItem) input;
+                return usageInput.getUsageName().equals(usage.getName()) &&
+                       usageInput.getStartDate().compareTo(startDate) >= 0 &&
+                       usageInput.getEndDate().compareTo(endDate) <= 0;
             }
         });
 
@@ -208,4 +207,125 @@ public class ContiguousInArrearUsageInterval {
         return billedAmount;
     }
 
+    private static class RolledUpUsageForUnitTypesFactory {
+
+        private final Map<String, RolledUpUsageForUnitTypes> map;
+
+        public RolledUpUsageForUnitTypesFactory(final List<RolledUpUsage> rolledUpUsages, final Set<String> unitTypes, final DateTimeZone accountTimeZone) {
+            map = new HashMap<String, RolledUpUsageForUnitTypes>();
+            for (RolledUpUsage ru : rolledUpUsages) {
+
+                final LocalDate startRolledUpDate = new LocalDate(ru.getStartTime(), accountTimeZone);
+                final LocalDate endRolledUpDate = new LocalDate(ru.getEndTime(), accountTimeZone);
+                final String key = startRolledUpDate + "-" + endRolledUpDate;
+
+                RolledUpUsageForUnitTypes usageForUnitTypes = map.get(key);
+                if (usageForUnitTypes == null) {
+                    usageForUnitTypes = new RolledUpUsageForUnitTypes(startRolledUpDate, endRolledUpDate, unitTypes);
+                    map.put(key, usageForUnitTypes);
+                }
+                usageForUnitTypes.addUsageForUnit(ru.getUnitType(), ru.getAmount());
+            }
+        }
+
+        public List<RolledUpUsageForUnitTypes> getOrderedRolledUpUsageForUnitTypes() {
+            final LinkedList<RolledUpUsageForUnitTypes> result = new LinkedList<RolledUpUsageForUnitTypes>(map.values());
+            Collections.sort(result);
+            return result;
+        }
+    }
+
+    /**
+     * Internal classes to transform RolledUpUsage into a map of usage (types, amount) across each billable interval.
+     */
+    private static class RolledUpUsageForUnitTypes implements Comparable {
+
+        private final LocalDate startDate;
+        private final LocalDate endDate;
+        private final Map<String, BigDecimal> unitAmounts;
+
+        private RolledUpUsageForUnitTypes(final LocalDate endDate, final LocalDate startDate, final Set<String> unitTypes) {
+            this.endDate = endDate;
+            this.startDate = startDate;
+            this.unitAmounts = new HashMap<String, BigDecimal>();
+            for (final String type : unitTypes) {
+                unitAmounts.put(type, BigDecimal.ZERO);
+            }
+        }
+
+        public void addUsageForUnit(final String unitType, BigDecimal amount) {
+            final BigDecimal currentAmount = unitAmounts.get(unitType);
+            unitAmounts.put(unitType, currentAmount.add(amount));
+        }
+
+        public LocalDate getStartDate() {
+            return startDate;
+        }
+
+        public LocalDate getEndDate() {
+            return endDate;
+        }
+
+        public BigDecimal getUsageAmountForUnitType(final String unitType) {
+            return unitAmounts.get(unitType);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = startDate != null ? startDate.hashCode() : 0;
+            result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+            result = 31 * result + (unitAmounts != null ? unitAmounts.hashCode() : 0);
+            return result;
+        }
+
+        @Override
+        public int compareTo(final Object o) {
+
+            Preconditions.checkArgument(o instanceof RolledUpUsageForUnitTypes);
+            final RolledUpUsageForUnitTypes other = (RolledUpUsageForUnitTypes) o;
+            // We will check later intervals don't overlap.
+            return getEndDate().compareTo(other.getStartDate());
+        }
+    }
+
+    public void addBillingEvent(final BillingEvent event) {
+        billingEvents.add(event);
+    }
+
+    public Usage getUsage() {
+        return usage;
+    }
+
+    public int getBCD() {
+        return billingEvents.get(0).getBillCycleDayLocal();
+    }
+
+    public UUID getAccountId() {
+        return billingEvents.get(0).getAccount().getId();
+    }
+
+    public UUID getBundleId() {
+        return billingEvents.get(0).getSubscription().getBundleId();
+    }
+
+    public UUID getSubscriptionId() {
+        return billingEvents.get(0).getSubscription().getId();
+    }
+
+    // STEPH_USAGE planName/phaseName,BCD,... might not be correct if we changed plan but Usage section was exactly similar
+    public String getPlanName() {
+        return billingEvents.get(0).getPlan().getName();
+    }
+
+    public String getPhaseName() {
+        return billingEvents.get(0).getPlanPhase().getName();
+    }
+
+    public Currency getCurrency() {
+        return billingEvents.get(0).getCurrency();
+    }
+
+    public DateTimeZone getAccountTimeZone() {
+        return billingEvents.get(0).getTimeZone();
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
index 14f932a..6736857 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionConsumableInArrear.java
@@ -17,8 +17,10 @@
 package org.killbill.billing.invoice.usage;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -40,127 +42,126 @@ import org.killbill.billing.util.callcontext.TenantContext;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 
+/**
+ * There is one such class created for each subscriptionId referenced in the billingEvents.
+ *
+ */
 public class SubscriptionConsumableInArrear {
 
-    private UUID invoiceId;
-    private final String unitType;
+    private final UUID invoiceId;
     private final List<BillingEvent> subscriptionBillingEvents;
     private final UsageUserApi usageApi;
     private final LocalDate targetDate;
     private final TenantContext context;
 
-    public SubscriptionConsumableInArrear(final UUID invoiceId, final String unitType, final List<BillingEvent> subscriptionBillingEvents, final UsageUserApi usageApi, final LocalDate targetDate, final TenantContext context) {
+    public SubscriptionConsumableInArrear(final UUID invoiceId, final List<BillingEvent> subscriptionBillingEvents, final UsageUserApi usageApi, final LocalDate targetDate, final TenantContext context) {
         this.invoiceId = invoiceId;
-        this.unitType = unitType;
         this.subscriptionBillingEvents = subscriptionBillingEvents;
         this.usageApi = usageApi;
         this.targetDate = targetDate;
         this.context = context;
     }
 
+    /**
+     * Based on billing events, (@code existingUsage} and targetDate, figure out what remains to be billed.
+     *
+     * @param existingUsage the existing on disk usage items.
+     * @return
+     * @throws CatalogApiException
+     */
     public List<InvoiceItem> computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage) throws CatalogApiException {
 
         final List<InvoiceItem> result = Lists.newLinkedList();
-        final List<ContiguousInArrearUsageInterval> billingEventTransitionTimePeriods = computeBillingEventTransitionTimePeriods();
+        final List<ContiguousInArrearUsageInterval> billingEventTransitionTimePeriods = computeInArrearUsageInterval();
         for (ContiguousInArrearUsageInterval usageInterval : billingEventTransitionTimePeriods) {
             result.addAll(usageInterval.computeMissingItems(existingUsage));
         }
         return result;
     }
 
+    List<ContiguousInArrearUsageInterval> computeInArrearUsageInterval() {
 
-    static List<TieredBlock> getTieredBlocks(final Usage usage, final String unitType) {
-
-        Preconditions.checkArgument(usage.getTiers().length > 0);
-
-        final List<TieredBlock> result = Lists.newLinkedList();
-        for (Tier tier : usage.getTiers()) {
-
-            for (TieredBlock tierBlock : tier.getTieredBlocks()) {
-                if (tierBlock.getUnit().getName().equals(unitType)) {
-                    result.add(tierBlock);
-                }
-            }
-        }
-        return result;
-    }
-
-    static DateTime localDateToEndOfDayInAccountTimezone(final LocalDate input, final DateTimeZone accountTimeZone) {
-        final DateTime dateTimeInAccountTimeZone = new DateTime(input.getYear(), input.getMonthOfYear(), input.getDayOfMonth(), 23, 59, 59, accountTimeZone);
-        return new DateTime(dateTimeInAccountTimeZone, DateTimeZone.UTC);
-    }
+        final List<ContiguousInArrearUsageInterval> usageIntervals = Lists.newLinkedList();
 
 
-    private List<ContiguousInArrearUsageInterval> computeBillingEventTransitionTimePeriods() {
+        final Map<String, ContiguousInArrearUsageInterval> inFlightInArrearUsageIntervals = new HashMap<String, ContiguousInArrearUsageInterval>();
+        for (BillingEvent event : subscriptionBillingEvents) {
 
-        final List<ContiguousInArrearUsageInterval> usageInterval = Lists.newLinkedList();
+            // All inflight usage interval are candidates to be closed unless we see that current billing event referencing the same usage section.
+            final Set<String> toBeClosed = inFlightInArrearUsageIntervals.keySet();
 
-        ContiguousInArrearUsageInterval existingInterval = null;
-        for (BillingEvent event : subscriptionBillingEvents) {
-            final Usage usage = findUsage(event);
-            if (usage == null || !usage.equals(existingInterval.getUsage())) {
-                if (existingInterval != null) {
-                    usageInterval.add(existingInterval.build(true));
-                    existingInterval = null;
-                }
-            }
+            // Extract all in arrear /consumable usage section for that billing event.
+            final List<Usage> usages = findConsumableInArrearUsages(event);
+            for (Usage usage : usages) {
 
-            if (usage != null) {
+                // Add inflight usage interval if non existent
+                ContiguousInArrearUsageInterval existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
                 if (existingInterval == null) {
-                    existingInterval = new ContiguousInArrearUsageInterval(usage, invoiceId, unitType, usageApi, targetDate, context);
+                    existingInterval = new ContiguousInArrearUsageInterval(usage, invoiceId, usageApi, targetDate, context);
+                    inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
                 }
+                // Add billing event for that usage interval
                 existingInterval.addBillingEvent(event);
+                // Remove usage interval for toBeClosed set
+                toBeClosed.remove(usage.getName());
+            }
+
+            // Build the usage interval that are no longer referenced
+            for (String usageName : toBeClosed) {
+                usageIntervals.add(inFlightInArrearUsageIntervals.remove(usageName).build(true));
             }
         }
-        if (existingInterval != null) {
-            usageInterval.add(existingInterval.build(false));
+        for (String usageName : inFlightInArrearUsageIntervals.keySet()) {
+            usageIntervals.add(inFlightInArrearUsageIntervals.remove(usageName).build(false));
         }
-        return usageInterval;
+        return usageIntervals;
     }
 
-    private Usage findUsage(final BillingEvent event) {
+    List<Usage> findConsumableInArrearUsages(final BillingEvent event) {
         if (event.getUsages().size() == 0) {
-            return null;
+            return Collections.emptyList();
         }
+
+        final List<Usage> result = Lists.newArrayList();
         for (Usage usage : event.getUsages()) {
             if (usage.getUsageType() != UsageType.CONSUMABLE ||
                 usage.getBillingMode() != BillingMode.IN_ARREAR) {
                 continue;
             }
-
-            List<TieredBlock> tieredBlock = getTieredBlocks(usage, unitType);
-            if (tieredBlock.size() > 0) {
-                return usage;
-            }
+            result.add(usage);
         }
-        return null;
+        return result;
     }
 
-    private void addMissingTransitionTimes(final List<LocalDate> transitionTimes, final List<UsageInvoiceItem> existingUsage) {
-
-        Preconditions.checkArgument(transitionTimes.size() > 0);
+    static List<TieredBlock> getTieredBlocks(final Usage usage, final String unitType) {
 
-        final LocalDate startDate = transitionTimes.get(0);
-        final LocalDate endDate = transitionTimes.get(transitionTimes.size() - 1);
+        Preconditions.checkArgument(usage.getTiers().length > 0);
 
-        for (UsageInvoiceItem ii : existingUsage) {
-            if (ii.getEndDate().compareTo(startDate) <= 0 || ii.getStartDate().compareTo(endDate) >= 0) {
-                continue;
-            }
-            if (ii.getStartDate().compareTo(startDate) < 0 && ii.getEndDate().compareTo(endDate) <= 0) {
-                transitionTimes.add(ii.getEndDate());
-            } else if (ii.getStartDate().compareTo(startDate) >= 0 && ii.getEndDate().compareTo(endDate) > 0) {
-                transitionTimes.add(ii.getStartDate());
-            } else {
-                transitionTimes.add(ii.getStartDate());
-                transitionTimes.add(ii.getEndDate());
+        final List<TieredBlock> result = Lists.newLinkedList();
+        for (Tier tier : usage.getTiers()) {
+            for (TieredBlock tierBlock : tier.getTieredBlocks()) {
+                if (tierBlock.getUnit().getName().equals(unitType)) {
+                    result.add(tierBlock);
+                }
             }
         }
+        return result;
+    }
 
-        final Set<LocalDate> uniqueTransitions = new HashSet<LocalDate>(transitionTimes);
-        transitionTimes.clear();
-        transitionTimes.addAll(uniqueTransitions);
-        Collections.sort(transitionTimes);
+    static DateTime localDateToEndOfDayInAccountTimezone(final LocalDate input, final DateTimeZone accountTimeZone) {
+        final DateTime dateTimeInAccountTimeZone = new DateTime(input.getYear(), input.getMonthOfYear(), input.getDayOfMonth(), 23, 59, 59, accountTimeZone);
+        return new DateTime(dateTimeInAccountTimeZone, DateTimeZone.UTC);
     }
 
+    static Set<String> getUnitTypes(final Usage usage) {
+        Preconditions.checkArgument(usage.getTiers().length > 0);
+
+        final Set<String> result = new HashSet<String>();
+        for (Tier tier : usage.getTiers()) {
+            for (TieredBlock tierBlock : tier.getTieredBlocks()) {
+                result.add(tierBlock.getUnit().getName());
+            }
+        }
+        return result;
+    }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 20879e6..faa4eb2 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -102,7 +102,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
                 return false;
             }
         };
-        this.generator = new DefaultInvoiceGenerator(clock, null, invoiceConfig);
+        this.generator = new DefaultInvoiceGenerator(clock, null, invoiceConfig, null);
     }
 
     @Test(groups = "fast")
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
index c917a59..112bb60 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
@@ -18,6 +18,7 @@ package org.killbill.billing.usage.api.user;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 import javax.inject.Inject;
@@ -60,8 +61,8 @@ public class DefaultUsageUserApi implements UsageUserApi {
     }
 
     @Override
-    public List<RolledUpUsage> getAllUsageForSubscription(final UUID subscriptionId, final String unitType, final List<DateTime> transitionTimes, final TenantContext tenantContext) {
-        // STEPH USAGE
+    public List<RolledUpUsage> getAllUsageForSubscription(final UUID subscriptionId, final Set<String> unitTypes, final List<DateTime> transitionTimes, final TenantContext tenantContext) {
+        // STEPH_USAGE
         return null;
     }
 }