killbill-aplcache

invoice: Rework usage detail 1. Modify json to make it different

2/10/2018 1:51:45 AM

Details

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 b9df9a5..2bf2213 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
@@ -46,24 +46,28 @@ import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.generator.BillingIntervalDetail;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetailInitializer;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.usage.api.RolledUpUnit;
 import org.killbill.billing.usage.api.RolledUpUsage;
 import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
@@ -72,8 +76,6 @@ import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearU
 import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearTieredBlocks;
 import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearUnitTypes;
 
-
-
 /**
  * There is one such class per subscriptionId, matching a given in arrear/consumable usage section and
  * referenced through a contiguous list of billing events.
@@ -208,56 +210,84 @@ public class ContiguousIntervalUsageInArrear {
             // 3. Computes total billed usage amount
             final BigDecimal billedUsage = computeBilledUsage(billedItems);
 
-            List<UsageInArrearDetail> toBeBilledUsageDetails = Lists.newLinkedList();
+            final List<RolledUpUnit> rolledUpUnits = ru.getRolledUpUnits();
+
+            UsageInArrearDetail toBeBilledUsageDetails = new UsageInArrearDetailInitializer();
             BigDecimal toBeBilledUsage;
             if (usage.getUsageType() == UsageType.CAPACITY) {
                 // In CAPACITY mode we build across units
-                toBeBilledUsageDetails.addAll(computeToBeBilledCapacityInArrear(ru.getRolledUpUnits(), areAllBilledItemsWithDetails));
-            } else /* UsageType.CONSUMABLE */{
+                toBeBilledUsageDetails = computeToBeBilledCapacityInArrear(rolledUpUnits);
+            } else /* UsageType.CONSUMABLE */ {
+
+                final Map<String, List<UsageConsumableInArrearTierUnitDetail>> previousUnitsUsage;
+                if (areAllBilledItemsWithDetails) {
+                    previousUnitsUsage = new HashMap<String, List<UsageConsumableInArrearTierUnitDetail>>();
+                    for (RolledUpUnit cur : rolledUpUnits) {
+                        final List<UsageConsumableInArrearTierUnitDetail> usageInArrearDetailForUnitType = getUsageConsumableInArrearDetailForUnitType(billedItems, cur.getUnitType());
+                        previousUnitsUsage.put(cur.getUnitType(), usageInArrearDetailForUnitType);
+                    }
+                } else {
+                    previousUnitsUsage = ImmutableMap.of();
+                }
+
                 // In CONSUMABLE mode we build for each unit independently, hence *for* loop
-                for (final RolledUpUnit cur : ru.getRolledUpUnits()) {
+                final List<UsageConsumableInArrearTierUnitDetail> usageConsumableInArrearTierUnitDetails = new ArrayList<UsageConsumableInArrearTierUnitDetail>();
+                for (final RolledUpUnit cur : rolledUpUnits) {
                     if (!unitTypes.contains(cur.getUnitType())) {
                         log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
                         continue;
                     }
-                    toBeBilledUsageDetails.addAll(computeToBeBilledConsumableInArrear(cur, areAllBilledItemsWithDetails));
+                    final List<UsageConsumableInArrearTierUnitDetail> previousUsage = previousUnitsUsage.containsKey(cur.getUnitType()) ? previousUnitsUsage.get(cur.getUnitType()) : ImmutableList.<UsageConsumableInArrearTierUnitDetail>of();
+
+                    usageConsumableInArrearTierUnitDetails.addAll(computeToBeBilledConsumableInArrear(cur, previousUsage, areAllBilledItemsWithDetails));
                 }
+                toBeBilledUsageDetails = new UsageConsumableInArrearDetail(usageConsumableInArrearTierUnitDetails);
             }
             // Based on details computed, compute total (proposed) amount
-            toBeBilledUsage = toBeBilledForUnit(toBeBilledUsageDetails);
-
+            toBeBilledUsage = toBeBilledUsageDetails.getAmount();
 
             // Compute final amount by subtracting  amount that was already billed.
             if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
                 final BigDecimal amountToBill;
-                if (areAllBilledItemsWithDetails) {
-                    toBeBilledUsageDetails = reconcileExistedBilledWithToBeBilled(billedItems, toBeBilledUsageDetails);
-                    amountToBill = toBeBilledForUnit(toBeBilledUsageDetails);
+                if (areAllBilledItemsWithDetails && usage.getUsageType() == UsageType.CONSUMABLE) {
+                    amountToBill = toBeBilledUsage;
                 } else {
-                    // If we don't have details from previous already billed usage, we can't reconcile each of the usage detail, so we just compute the overall price
-                    amountToBill = toBeBilledForUnit(toBeBilledUsageDetails).subtract(billedUsage);
+                    amountToBill = toBeBilledUsage.subtract(billedUsage);
                 }
 
                 if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
-                    if (UsageDetailMode.DETAIL == usageDetailMode && usage.getUsageType() == UsageType.CONSUMABLE){
-                        for (UsageInArrearDetail toBeBilledUsageDetail : toBeBilledUsageDetails){
+                    if (UsageDetailMode.DETAIL == usageDetailMode && usage.getUsageType() == UsageType.CONSUMABLE) {
+                        for (UsageConsumableInArrearTierUnitDetail toBeBilledUsageDetail : ((UsageConsumableInArrearDetail) toBeBilledUsageDetails).getTierDetails()) {
                             final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
-                                                                          getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(),toBeBilledUsageDetail.getQuantity(),null);
+                                                                          getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), null);
                             result.add(item);
                         }
                     } else {
+                        final String itemDetails = areAllBilledItemsWithDetails ? toBeBilledUsageDetails.toJson(objectMapper) : null;
                         final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
-                                                                      getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, null, getCurrency(),null, toJson(toBeBilledUsageDetails));
+                                                                      getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, null, getCurrency(), null, itemDetails);
                         result.add(item);
                     }
                 }
             }
         }
-
         final LocalDate nextNotificationdate = computeNextNotificationDate();
         return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationdate);
     }
 
+    private List<UsageConsumableInArrearTierUnitDetail> getUsageConsumableInArrearDetailForUnitType(final Iterable<InvoiceItem> billedItems, final String unitType) {
+
+        final List<UsageConsumableInArrearTierUnitDetail> result = new ArrayList<UsageConsumableInArrearTierUnitDetail>();
+        for (final InvoiceItem bi : billedItems) {
+            final List<UsageConsumableInArrearTierUnitDetail> billedUsageItemDetails = fromJson(bi.getItemDetails());
+            for (final UsageConsumableInArrearTierUnitDetail curDetail : billedUsageItemDetails) {
+                if (curDetail.getTierUnit().equals(unitType)) {
+                    result.add(curDetail);
+                }
+            }
+        }
+        return result;
+    }
 
     private boolean areAllBilledItemsWithDetails(final Iterable<InvoiceItem> billedItems) {
         boolean atLeastOneItemWithoutDetails = Iterables.any(billedItems, new Predicate<InvoiceItem>() {
@@ -335,7 +365,7 @@ public class ContiguousIntervalUsageInArrear {
                 if (prevRawUsage != null) {
                     if (prevRawUsage.getDate().compareTo(prevDate) >= 0 && prevRawUsage.getDate().compareTo(curDate) < 0) {
                         final Long currentAmount = perRangeUnitToAmount.get(prevRawUsage.getUnitType());
-                        final Long updatedAmount =  computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
+                        final Long updatedAmount = computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
                         perRangeUnitToAmount.put(prevRawUsage.getUnitType(), updatedAmount);
                         prevRawUsage = null;
                     }
@@ -356,7 +386,7 @@ public class ContiguousIntervalUsageInArrear {
                         }
 
                         final Long currentAmount = perRangeUnitToAmount.get(curRawUsage.getUnitType());
-                        final Long updatedAmount =  computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
+                        final Long updatedAmount = computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
                         perRangeUnitToAmount.put(curRawUsage.getUnitType(), updatedAmount);
                     }
                 }
@@ -394,7 +424,6 @@ public class ContiguousIntervalUsageInArrear {
         }
     }
 
-
     private Limit getTierLimit(final Tier tier, final String unitType) {
         for (final Limit cur : tier.getLimits()) {
             if (cur.getUnit().getName().equals(unitType)) {
@@ -411,14 +440,14 @@ public class ContiguousIntervalUsageInArrear {
      * @throws CatalogApiException
      */
     @VisibleForTesting
-    List<UsageInArrearDetail> computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+    UsageCapacityInArrearDetail computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits) throws CatalogApiException {
         Preconditions.checkState(isBuilt.get());
 
         final List<Tier> tiers = getCapacityInArrearTier(usage);
 
         final Set<String> perUnitTypeDetailTierLevel = new HashSet<String>();
         int tierNum = 0;
-        final List<UsageInArrearDetail> toBeBilledDetails = Lists.newLinkedList();
+        final List<UsageInArrearTierUnitDetail> toBeBilledDetails = Lists.newLinkedList();
         for (final Tier cur : tiers) {
             tierNum++;
             boolean complies = true;
@@ -430,20 +459,19 @@ public class ContiguousIntervalUsageInArrear {
                     complies = false;
                 } else {
                     if (!perUnitTypeDetailTierLevel.contains(ro.getUnitType())) {
-                        toBeBilledDetails.add(new UsageInArrearDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), ro.getAmount().intValue(), BigDecimal.ZERO, BigDecimal.ZERO, areAllBilledItemsWithDetails, ""));
+                        toBeBilledDetails.add(new UsageConsumableInArrearTierUnitDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), 1, ro.getAmount().intValue(), BigDecimal.ZERO));
                         perUnitTypeDetailTierLevel.add(ro.getUnitType());
                     }
                 }
             }
             if (complies) {
-                toBeBilledDetails.get(toBeBilledDetails.size() - 1).setAmount(cur.getRecurringPrice().getPrice(getCurrency()));
-                return toBeBilledDetails;
+                return new UsageCapacityInArrearDetail(toBeBilledDetails, cur.getRecurringPrice().getPrice(getCurrency()));
             }
         }
         // Probably invalid catalog config
         final Joiner joiner = Joiner.on(", ");
         joiner.join(roUnits);
-        Preconditions.checkState(false, "Could not find tier for usage " + usage.getName()+ "matching with data = " + joiner.join(roUnits));
+        Preconditions.checkState(false, "Could not find tier for usage " + usage.getName() + "matching with data = " + joiner.join(roUnits));
         return null;
     }
 
@@ -453,33 +481,36 @@ public class ContiguousIntervalUsageInArrear {
      * @throws CatalogApiException
      */
     @VisibleForTesting
-    List<UsageInArrearDetail> computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+    List<UsageConsumableInArrearTierUnitDetail> computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
 
         Preconditions.checkState(isBuilt.get());
         final List<TieredBlock> tieredBlocks = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType());
 
         switch (usage.getTierBlockPolicy()) {
             case ALL_TIERS:
-                return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, roUnit.getAmount(), areAllBilledItemsWithDetails);
+                return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, previousUsage, roUnit.getAmount());
             case TOP_TIER:
-                return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, roUnit.getAmount(), areAllBilledItemsWithDetails));
+                return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, previousUsage, roUnit.getAmount(), areAllBilledItemsWithDetails));
             default:
                 throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy());
         }
     }
 
+    List<UsageConsumableInArrearTierUnitDetail> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final Long units) throws CatalogApiException {
 
-    List<UsageInArrearDetail> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final Long units, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
-
-        List<UsageInArrearDetail> toBeBilledDetails = Lists.newLinkedList();
+        List<UsageConsumableInArrearTierUnitDetail> toBeBilledDetails = Lists.newLinkedList();
         int remainingUnits = units.intValue();
         int tierNum = 0;
+
+        final int lastPreviousUsageTier = previousUsage.size(); // we count tier from 1, 2, ...
+        final boolean hasPreviousUsage = lastPreviousUsageTier > 0;
+
         for (final TieredBlock tieredBlock : tieredBlocks) {
 
             tierNum++;
             final int blockTierSize = tieredBlock.getSize().intValue();
             final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
-            final int nbUsedTierBlocks;
+            int nbUsedTierBlocks;
             if (tmp > tieredBlock.getMax()) {
                 nbUsedTierBlocks = tieredBlock.getMax().intValue();
                 remainingUnits -= tieredBlock.getMax() * blockTierSize;
@@ -489,13 +520,26 @@ public class ContiguousIntervalUsageInArrear {
             }
 
             if (nbUsedTierBlocks > 0) {
-                toBeBilledDetails.add(new UsageInArrearDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), nbUsedTierBlocks, areAllBilledItemsWithDetails));
+                if (hasPreviousUsage) {
+                    final Integer previousUsageQuantity = tierNum <= lastPreviousUsageTier ? previousUsage.get(tierNum - 1).getQuantity() : 0;
+                    if (tierNum < lastPreviousUsageTier) {
+                        Preconditions.checkState(nbUsedTierBlocks == previousUsageQuantity, String.format("Expected usage for tier='%d', unit='%s' to be full, instead found units='[%d/%d]'",
+                                                                                                          tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
+                    } else {
+                        Preconditions.checkState(nbUsedTierBlocks - previousUsageQuantity >= 0, String.format("Expected usage for tier='%d', unit='%s' to contain at least as mush as current usage, instead found units='[%d/%d]",
+                                                                                                              tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantityq));
+                    }
+                    nbUsedTierBlocks = nbUsedTierBlocks - previousUsageQuantity;
+                }
+                if (nbUsedTierBlocks > 0) {
+                    toBeBilledDetails.add(new UsageConsumableInArrearTierUnitDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), blockTierSize, nbUsedTierBlocks));
+                }
             }
         }
         return toBeBilledDetails;
     }
 
-    UsageInArrearDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final Long units, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+    UsageConsumableInArrearTierUnitDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final Long units, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
 
         int remainingUnits = units.intValue();
 
@@ -520,10 +564,9 @@ public class ContiguousIntervalUsageInArrear {
         final int lastBlockTierSize = targetBlock.getSize().intValue();
         final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
 
-        return new UsageInArrearDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), nbBlocks, areAllBilledItemsWithDetails);
+        return new UsageConsumableInArrearTierUnitDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), targetBlock.getSize().intValue(), nbBlocks);
     }
 
-
     /**
      * @param filteredUsageForInterval the list of invoiceItem to consider
      * @return the price amount that was already billed for that period and usage section (across unitTypes)
@@ -628,159 +671,29 @@ public class ContiguousIntervalUsageInArrear {
         }
     }
 
-    public BigDecimal toBeBilledForUnit(List<UsageInArrearDetail> toBeBilledDetails){
-        BigDecimal result = BigDecimal.ZERO;
-        for (UsageInArrearDetail toBeBilled: toBeBilledDetails){
-            result = result.add(toBeBilled.getAmount());
-        }
-        return result;
-    }
-
-    private List<UsageInArrearDetail> reconcileExistedBilledWithToBeBilled(final Iterable<InvoiceItem> billedItems, final List<UsageInArrearDetail> toBeBilledUsageInArrearDetails) {
-        for (final InvoiceItem bi : billedItems) {
-            List<UsageInArrearDetail> billedUsageItemDetails = fromJson(bi.getItemDetails());
-
-            Preconditions.checkState(billedUsageItemDetails != null && billedUsageItemDetails.size() > 0);
-
-            for (final UsageInArrearDetail toBeBilledConsumable : toBeBilledUsageInArrearDetails) {
-                billedUsageItemDetails = toBeBilledConsumable.reconcile(billedUsageItemDetails);
-            }
-
-            if (billedUsageItemDetails != null && billedUsageItemDetails.size() > 0) {
-                for (final UsageInArrearDetail billedUsage : billedUsageItemDetails) {
-                    toBeBilledUsageInArrearDetails.add(new UsageInArrearDetail(billedUsage.getTier(), billedUsage.getTierUnit(), billedUsage.getTierPrice(),
-                                                                               billedUsage.getQuantity() * -1, billedUsage.getAmount().negate(), null, true, bi.getId().toString()));
-                }
+    public BigDecimal toBeBilledForUnit(final List<UsageConsumableInArrearTierUnitDetail> toBeBilledDetails, final UsageType usageType) {
+        if (usageType == UsageType.CAPACITY) {
+            return toBeBilledDetails.get(0).getAmount();
+        } else {
+            BigDecimal result = BigDecimal.ZERO;
+            for (UsageConsumableInArrearTierUnitDetail toBeBilled : toBeBilledDetails) {
+                result = result.add(toBeBilled.getAmount());
             }
+            return result;
         }
-        return toBeBilledUsageInArrearDetails;
     }
 
-    private static String toJson(final List<UsageInArrearDetail> toBeBilledUsageInArrearDetails) {
-        String result = null;
-        if (toBeBilledUsageInArrearDetails != null && toBeBilledUsageInArrearDetails.size() > 0){
+    public static List<UsageConsumableInArrearTierUnitDetail> fromJson(String itemDetails) {
+        List<UsageConsumableInArrearTierUnitDetail> toBeBilledUsageConsumableInArrearTierUnitDetails = null;
+        if (itemDetails != null) {
             try {
-                result = objectMapper.writeValueAsString(toBeBilledUsageInArrearDetails);
-            } catch (JsonProcessingException e) {
-                Preconditions.checkState(false, e.getMessage());
-            }
-        }
-        return result;
-    }
-
-    private static List<UsageInArrearDetail> fromJson(String itemDetails){
-        List<UsageInArrearDetail> toBeBilledUsageInArrearDetails = null;
-        if (itemDetails != null){
-            try {
-                toBeBilledUsageInArrearDetails = objectMapper.readValue(itemDetails, new TypeReference<List<UsageInArrearDetail>>() {});
+                toBeBilledUsageConsumableInArrearTierUnitDetails = objectMapper.readValue(itemDetails, new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
             } catch (IOException e) {
                 Preconditions.checkState(false, e.getMessage());
             }
         }
 
-        return toBeBilledUsageInArrearDetails;
+        return toBeBilledUsageConsumableInArrearTierUnitDetails;
     }
 
-    public static class UsageInArrearDetail {
-
-        private final int tier;
-        private final String tierUnit;
-        private final BigDecimal tierPrice;
-        private Integer quantity;
-        private String reference;
-        private BigDecimal previousBilledAmount;
-        private boolean isPreviousBilledAmountAvailable;
-        private BigDecimal amount;
-
-        public UsageInArrearDetail(int tier, String tierUnit, BigDecimal tierPrice, Integer quantity, boolean isPreviousBilledAmountAvailable) {
-            this(tier, tierUnit, tierPrice, quantity, tierPrice.multiply(new BigDecimal(quantity)), BigDecimal.ZERO, isPreviousBilledAmountAvailable, "");
-        }
-
-        @JsonCreator
-        public UsageInArrearDetail(@JsonProperty("tier") int tier,
-                                   @JsonProperty("tierUnit") String tierUnit,
-                                   @JsonProperty("tierPrice") BigDecimal tierPrice,
-                                   @JsonProperty("quantity") Integer quantity,
-                                   @JsonProperty("amount") BigDecimal amount,
-                                    @JsonProperty("previousBilledAmount") BigDecimal previousBilledAmount,
-                                   @JsonProperty("isPreviousBilledAmountAvailable") boolean isPreviousBilledAmountAvailable,
-                                   @JsonProperty("reference") String reference) {
-            this.tier = tier;
-            this.tierUnit = tierUnit;
-            this.tierPrice = tierPrice;
-            this.quantity = quantity;
-            this.amount = amount;
-            this.previousBilledAmount = previousBilledAmount;
-            this.isPreviousBilledAmountAvailable = isPreviousBilledAmountAvailable;
-            this.reference = reference;
-        }
-
-        public int getTier() {
-            return tier;
-        }
-
-        public String getTierUnit() {
-            return tierUnit;
-        }
-
-        public BigDecimal getTierPrice() {
-            return tierPrice;
-        }
-
-        public Integer getQuantity() {
-            return quantity;
-        }
-
-        public BigDecimal getPreviousBilledAmount() {
-            return previousBilledAmount;
-        }
-
-        public String getReference() {
-            return reference;
-        }
-
-        public void setPreviousBilledAmount(BigDecimal previousBilledAmount) {
-            this.previousBilledAmount = previousBilledAmount;
-        }
-
-        public boolean isPreviousBilledAmountAvailable() {
-            return isPreviousBilledAmountAvailable;
-        }
-
-        public void setPreviousBilledAmountAvailable(final boolean previousBilledAmountAvailable) {
-            isPreviousBilledAmountAvailable = previousBilledAmountAvailable;
-        }
-
-        public BigDecimal getAmount() {
-            return amount;
-        }
-
-        public void setQuantity(Integer quantity) {
-            this.quantity = quantity;
-        }
-
-        public void setAmount(BigDecimal amount) {
-            this.amount = amount;
-        }
-
-        public void setReference(String reference) {
-            this.reference = reference;
-        }
-
-        public List<UsageInArrearDetail> reconcile(final List<UsageInArrearDetail> billedUsageItemDetails) {
-
-            final List<UsageInArrearDetail> unreconciledUsage = Lists.newLinkedList();
-            for (UsageInArrearDetail billedUsageDetail : billedUsageItemDetails) {
-                if (tierUnit.equals(billedUsageDetail.tierUnit)) {
-                    previousBilledAmount = billedUsageDetail.getAmount().abs();
-                    quantity = quantity - billedUsageDetail.getQuantity();
-                    amount = amount.subtract(previousBilledAmount);
-                } else {
-                    unreconciledUsage.add(billedUsageDetail);
-                }
-            }
-
-            return unreconciledUsage;
-        }
-    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java
new file mode 100644
index 0000000..2030690
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Preconditions;
+
+public class UsageCapacityInArrearDetail implements UsageInArrearDetail {
+
+    private final List<UsageInArrearTierUnitDetail> tierDetails;
+    private BigDecimal amount;
+
+    @JsonCreator
+    public UsageCapacityInArrearDetail(@JsonProperty("tierDetails") List<UsageInArrearTierUnitDetail> tierDetails,
+                                       @JsonProperty("amount") BigDecimal amount) {
+        this.tierDetails = tierDetails;
+        this.amount = amount;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public List<UsageInArrearTierUnitDetail> getTierDetails() {
+        return tierDetails;
+    }
+
+    // TODO STEPH optimize
+    @Override
+    public String toJson(final ObjectMapper objectMapper) {
+        String result = null;
+        if (tierDetails != null && tierDetails.size() > 0){
+            try {
+                result = objectMapper.writeValueAsString(tierDetails);
+            } catch (JsonProcessingException e) {
+                Preconditions.checkState(false, e.getMessage());
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java
new file mode 100644
index 0000000..903e489
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Preconditions;
+
+public class UsageConsumableInArrearDetail implements UsageInArrearDetail {
+
+    private final List<UsageConsumableInArrearTierUnitDetail> tierDetails;
+    private BigDecimal amount;
+
+    public UsageConsumableInArrearDetail(@JsonProperty("tierDetails") List<UsageConsumableInArrearTierUnitDetail> tierDetails) {
+        this(tierDetails, computeAmount(tierDetails));
+    }
+
+    @JsonCreator
+    public UsageConsumableInArrearDetail(@JsonProperty("tierDetails") List<UsageConsumableInArrearTierUnitDetail> tierDetails,
+                                         @JsonProperty("amount") BigDecimal amount) {
+        this.tierDetails = tierDetails;
+        this.amount = amount;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public String toJson(final ObjectMapper objectMapper) {
+        String result = null;
+        if (tierDetails != null && tierDetails.size() > 0){
+            try {
+                result = objectMapper.writeValueAsString(tierDetails);
+            } catch (JsonProcessingException e) {
+                Preconditions.checkState(false, e.getMessage());
+            }
+        }
+        return result;
+    }
+
+    public List<UsageConsumableInArrearTierUnitDetail> getTierDetails() {
+        return tierDetails;
+    }
+
+    private static BigDecimal computeAmount(final List<UsageConsumableInArrearTierUnitDetail> tierDetails) {
+        BigDecimal result = BigDecimal.ZERO;
+        for (UsageConsumableInArrearTierUnitDetail toBeBilled : tierDetails) {
+            result = result.add(toBeBilled.getAmount());
+        }
+        return result;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java
new file mode 100644
index 0000000..de7b59b
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageConsumableInArrearTierUnitDetail extends UsageInArrearTierUnitDetail {
+
+    private final int tierBlockSize;
+    private BigDecimal amount;
+
+    public UsageConsumableInArrearTierUnitDetail(int tier, String tierUnit, BigDecimal tierPrice, Integer tierBlockSize, Integer quantity) {
+        this(tier, tierUnit, tierPrice, tierBlockSize, quantity, tierPrice.multiply(new BigDecimal(quantity)));
+    }
+
+    @JsonCreator
+    public UsageConsumableInArrearTierUnitDetail(@JsonProperty("tier") int tier,
+                                                 @JsonProperty("tierUnit") String tierUnit,
+                                                 @JsonProperty("tierPrice") BigDecimal tierPrice,
+                                                 @JsonProperty("tierBlockSize") Integer tierBlockSize,
+                                                 @JsonProperty("quantity") Integer quantity,
+                                                 @JsonProperty("amount") BigDecimal amount) {
+        super(tier, tierUnit, tierPrice, quantity);
+        this.amount = amount;
+        this.tierBlockSize = tierBlockSize;
+    }
+
+    public int getTier() {
+        return tier;
+    }
+
+    public String getTierUnit() {
+        return tierUnit;
+    }
+
+    public BigDecimal getTierPrice() {
+        return tierPrice;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public int getTierBlockSize() {
+        return tierBlockSize;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java
new file mode 100644
index 0000000..a9dfdba
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+public interface UsageInArrearDetail {
+
+    BigDecimal getAmount();
+
+    String toJson(ObjectMapper mapper);
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetailInitializer.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetailInitializer.java
new file mode 100644
index 0000000..1b441f7
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetailInitializer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+public class UsageInArrearDetailInitializer implements UsageInArrearDetail {
+
+    @Override
+    public BigDecimal getAmount() {
+        return BigDecimal.ZERO;
+    }
+
+    @Override
+    public String toJson(final ObjectMapper mapper) {
+        return null;
+    }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
new file mode 100644
index 0000000..59aebc7
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageInArrearTierUnitDetail {
+
+    protected final int tier;
+    protected final String tierUnit;
+    protected final BigDecimal tierPrice;
+    protected Integer quantity;
+
+    @JsonCreator
+    public UsageInArrearTierUnitDetail(@JsonProperty("tier") int tier,
+                                       @JsonProperty("tierUnit") String tierUnit,
+                                       @JsonProperty("tierPrice") BigDecimal tierPrice,
+                                       @JsonProperty("quantity") Integer quantity) {
+        this.tier = tier;
+        this.tierUnit = tierUnit;
+        this.tierPrice = tierPrice;
+        this.quantity = quantity;
+    }
+
+    public int getTier() {
+        return tier;
+    }
+
+    public String getTierUnit() {
+        return tierUnit;
+    }
+
+    public BigDecimal getTierPrice() {
+        return tierPrice;
+    }
+
+    public Integer getQuantity() {
+        return quantity;
+    }
+}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
index 357592f..2507d7d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
@@ -27,19 +27,20 @@ import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.DefaultLimit;
 import org.killbill.billing.catalog.DefaultTier;
-import org.killbill.billing.catalog.DefaultTieredBlock;
 import org.killbill.billing.catalog.DefaultUnit;
 import org.killbill.billing.catalog.DefaultUsage;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.catalog.api.UsageType;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
-import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearDetail;
 import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.usage.api.RolledUpUnit;
@@ -149,32 +150,32 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
                                                                                                                                                   Collections.<Usage>emptyList())
                                                                                                                      );
         // Tier 1 (both units from tier 1)
-        List<UsageInArrearDetail> result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
-                                                                                                                                     new DefaultRolledUpUnit("unit2", 1000L),
-                                                                                                                                     new DefaultRolledUpUnit("unit3", 50L)), true);
-        assertEquals(result.size(), 3);
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(BigDecimal.TEN) == 0);
+        UsageCapacityInArrearDetail result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
+                                                                                                                                       new DefaultRolledUpUnit("unit2", 1000L),
+                                                                                                                                       new DefaultRolledUpUnit("unit3", 50L)));
+        assertEquals(result.getTierDetails().size(), 3);
+        assertTrue(result.getAmount().compareTo(BigDecimal.TEN) == 0);
 
         // Tier 2 (only one unit from tier 1)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
-                                                                                                           new DefaultRolledUpUnit("unit2", 1001L)), true);
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+                                                                                                           new DefaultRolledUpUnit("unit2", 1001L)));
+        assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
 
         // Tier 2 (only one unit from tier 1)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 101L),
-                                                                                                           new DefaultRolledUpUnit("unit2", 1000L)), true);
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+                                                                                                           new DefaultRolledUpUnit("unit2", 1000L)));
+        assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
 
 
         // Tier 2 (both units from tier 2)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 101L),
-                                                                                                           new DefaultRolledUpUnit("unit2", 1001L)), true);
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+                                                                                                           new DefaultRolledUpUnit("unit2", 1001L)));
+        assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
 
         // Tier 3 (only one unit from tier 3)
         result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 10L),
-                                                                                                           new DefaultRolledUpUnit("unit2", 2001L)), true);
-        assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("30.0")) == 0);
+                                                                                                           new DefaultRolledUpUnit("unit2", 2001L)));
+        assertTrue(result.getAmount().compareTo(new BigDecimal("30.0")) == 0);
     }
 
     @Test(groups = "fast")
@@ -184,23 +185,37 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
         final LocalDate endDate = new LocalDate(2014, 05, 15);
 
-        // 2 items for startDate - firstBCDDate
         final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
-        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit", 130L));
-        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit", 271L));
-        // 1 items for firstBCDDate - endDate
-        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit", 199L));
+        //
+        // First period: startDate - firstBCDDate
+        //
+        // 2 items for unit1
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit1", 130L));
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit1", 271L));
+        // 1 items for unit2
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 24), "unit2", 10L));
+
+        //
+        // Second period: firstBCDDate - endDate
+        //
+        // 1 items unit1
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit1", 199L));
+        // 1 items unit2
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit2", 20L));
 
-        final DefaultUnit unit = new DefaultUnit().setName("unit");
-        final DefaultLimit limit = new DefaultLimit().setUnit(unit).setMax((double) -1);
+        final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
+        final DefaultLimit limit1 = new DefaultLimit().setUnit(unit1).setMax((double) -1);
+
+        final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
+        final DefaultLimit limit2 = new DefaultLimit().setUnit(unit2).setMax((double) -1);
 
-        final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit);
+
+        final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit1, limit2);
 
         final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
 
 
         final LocalDate targetDate = endDate;
-
         final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
         final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
 
@@ -249,29 +264,27 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsAggregateMode() throws CatalogApiException, IOException {
-
-        testComputeMissingItemsByMode(UsageDetailMode.AGGREGATE);
+    public void testMultipleItemsAndTiersAggregateMode() throws CatalogApiException, IOException {
+        testMultipleItemsAndTiers(UsageDetailMode.AGGREGATE);
     }
 
     @Test(groups = "fast")
-    public void testComputeMissingItemsDetailMode() throws CatalogApiException, IOException {
-
-       testComputeMissingItemsByMode(UsageDetailMode.DETAIL);
+    public void testMultipleItemsAndTiersDetailMode() throws CatalogApiException, IOException {
+        testMultipleItemsAndTiers(UsageDetailMode.DETAIL);
     }
 
-    private void testComputeMissingItemsByMode(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException {
+    private void testMultipleItemsAndTiers(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException {
 
         // Case 1
         List<RawUsage> rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, usageDetailMode);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(BigDecimal.ONE),0, String.format("%s != 1.0", result.get(0).getAmount()));
 
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR item detail
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -287,11 +300,11 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
-        result = produceInvoiceItems(rawUsages, usageDetailMode);
+        result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(BigDecimal.TEN),0, String.format("%s != 10.0", result.get(0).getAmount()));
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // FOO item detail
         assertEquals(itemDetails.get(0).getTierUnit(), "FOO");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -308,11 +321,11 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         rawUsages = new ArrayList<RawUsage>();
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
-        result = produceInvoiceItems(rawUsages, usageDetailMode);
+        result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("100.0")),0, String.format("%s != 100.0", result.get(0).getAmount()));
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR item detail
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 2);
@@ -325,7 +338,49 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
         assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
     }
 
-    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, UsageDetailMode usageDetailMode) throws CatalogApiException {
+    @Test(groups = "fast")
+    public void testMultipleItemsAndTiersWithExistingItems() throws CatalogApiException, IOException {
+
+
+        // let's assume we have some existing usage
+        final UsageInArrearTierUnitDetail existingFooUsageTier1 = new UsageConsumableInArrearTierUnitDetail(1, "FOO", BigDecimal.ONE, 1, 9, BigDecimal.TEN);
+        final UsageInArrearTierUnitDetail existingBarUsageTier2 = new UsageConsumableInArrearTierUnitDetail(2, "BAR", BigDecimal.TEN, 1, 200, BigDecimal.TEN);
+
+
+        List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 60L)); // tier 3
+        rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 200L)); // tier 2
+
+
+        final List<UsageInArrearTierUnitDetail> existingUsage = ImmutableList.of(existingFooUsageTier1, existingBarUsageTier2);
+
+        final String existingUsageJson = new UsageCapacityInArrearDetail(existingUsage, BigDecimal.TEN).toJson(objectMapper);
+
+
+        final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), BigDecimal.TEN, null, currency, null, existingUsageJson);
+        existingItems.add(ii1);
+
+
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, UsageDetailMode.AGGREGATE, existingItems);
+        assertEquals(result.size(), 1);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("90.00")), 0, String.format("%s != 90.0", result.get(0).getAmount()));
+
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
+        // BAR item detail
+        assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+        assertEquals(itemDetails.get(0).getTier(), 2);
+        assertEquals(itemDetails.get(0).getQuantity().intValue(), 200);
+        assertEquals(itemDetails.get(0).getTierPrice().compareTo(BigDecimal.TEN), 0);
+        // FOO item detail
+        assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
+        assertEquals(itemDetails.get(1).getTier(), 3);
+        assertEquals(itemDetails.get(1).getQuantity().intValue(), 60);
+        assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+    }
+
+
+    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, UsageDetailMode usageDetailMode, List<InvoiceItem> existingItems) throws CatalogApiException {
 
         final LocalDate startDate = new LocalDate(2014, 03, 20);
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -355,7 +410,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
 
         final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
 
-        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of());
+        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
         final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
         final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
             @Override
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 8c81715..70909e6 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
@@ -22,10 +22,7 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
@@ -37,11 +34,13 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.TierBlockPolicy;
 import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.catalog.api.UsageType;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
-import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearDetail;
 import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitDetail;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.usage.api.RolledUpUsage;
@@ -52,10 +51,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
@@ -65,7 +61,7 @@ import com.google.common.collect.Ordering;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
-import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNull;
 
 public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearBase {
 
@@ -150,7 +146,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
 
-        List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), true);
+        List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         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"));
@@ -178,7 +174,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
 
-        List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L), true);
+        List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(result.size(), 2);
 
         // 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15
@@ -212,23 +208,23 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         //
         // 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<UsageInArrearDetail> inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L), true);
+        List<UsageConsumableInArrearTierUnitDetail> inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputTier1.size(), 1);
         // 1000 units => (tier1) : 1000 / 100 + 1000 % 100 = 10
         assertEquals(inputTier1.get(0).getAmount(), new BigDecimal("10"));
 
-        List<UsageInArrearDetail> inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L), true);
+        List<UsageConsumableInArrearTierUnitDetail> inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputTier2.size(), 1);
         // 101000 units => (tier2) :  101000 / 1000 + 101000 % 1000 = 101 + 0 = 101
         assertEquals(inputTier2.get(0).getAmount(), new BigDecimal("101"));
 
-        List<UsageInArrearDetail> inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L), true);
+        List<UsageConsumableInArrearTierUnitDetail> inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         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<UsageInArrearDetail> inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L), true);
+        List<UsageConsumableInArrearTierUnitDetail> inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(inputLastTier.size(), 1);
         // 300000 units => (tier3) : 300000 / 1000 + 300000 % 1000 = 300 units => $150
         assertEquals(inputLastTier.get(0).getAmount(), new BigDecimal("150.0"));
@@ -257,7 +253,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
 
-        List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), true);
+        List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
         assertEquals(result.size(), 1);
 
         // 111 = 111 * 0.5 =
@@ -322,12 +318,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertTrue(result.get(0).getStartDate().compareTo(startDate) == 0);
         assertTrue(result.get(0).getEndDate().compareTo(firstBCDDate) == 0);
 
-        // check item detail
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
-
-        assertEquals(itemDetails.size(), 1);
-        assertFalse(itemDetails.get(0).isPreviousBilledAmountAvailable());
-        assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("5.0")),0);
+        // Because the existing items did not have any json (item details), the new one also don't have the details.
+        assertNull(result.get(0).getItemDetails());
 
         // Invoiced for 1 BTC and used 199  => 2 blocks => 2 BTC so remaining piece should be 1 BTC
         assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("1.0")), 0, String.format("%s != 1.0", result.get(0).getAmount()));
@@ -340,12 +332,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertEquals(result.get(1).getUsageName(), usage.getName());
         assertTrue(result.get(1).getStartDate().compareTo(firstBCDDate) == 0);
         assertTrue(result.get(1).getEndDate().compareTo(endDate) == 0);
-
-        // check item detail
-        List<UsageInArrearDetail> itemDetails2 = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
-        assertEquals(itemDetails2.size(), 1);
-        assertFalse(itemDetails2.get(0).isPreviousBilledAmountAvailable());
-        assertEquals(itemDetails2.get(0).getAmount().compareTo(new BigDecimal("2.0")),0);
+        assertNull(result.get(1).getItemDetails());
     }
 
     @Test(groups = "fast")
@@ -461,12 +448,12 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
                                                                                                                                              BillingPeriod.MONTHLY,
                                                                                                                                              Collections.<Usage>emptyList())
                                                                                                                      );
-        List<UsageInArrearDetail> results = Lists.newArrayList();
-        results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L), true));
-        results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L), true));
+        List<UsageConsumableInArrearTierUnitDetail> results = Lists.newArrayList();
+        results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true));
+        results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true));
         assertEquals(results.size(), 2);
 
-        assertEquals(intervalConsumableInArrear.toBeBilledForUnit(results), new BigDecimal("18.5"));
+        assertEquals(intervalConsumableInArrear.toBeBilledForUnit(results, UsageType.CONSUMABLE), new BigDecimal("18.5"));
     }
 
     @Test(groups = "fast")
@@ -477,11 +464,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")),0);
 
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR: 99 * 2 = 198
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -500,11 +487,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("225")),0);
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR: 100 * 2 = 200
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -529,11 +516,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2230")),0);
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR: 100 * 2 = 200
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -574,7 +561,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
         // BAR: 99 * 2 = 198
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
@@ -590,7 +577,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 3);
        // BAR: 100 * 2 = 200
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
@@ -610,7 +597,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 5);
        // BAR: 100 * 2 = 200
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
@@ -642,11 +629,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")),0);
 
-        List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR: 99 * 2 = 198
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 1);
@@ -665,11 +652,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2025")),0);
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR: 101 * 20 = 2020
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 2);
@@ -688,11 +675,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9620")),0);
 
-        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+        itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
         // BAR: 101 * 20 = 2020
         assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
         assertEquals(itemDetails.get(0).getTier(), 2);
@@ -715,7 +702,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
 
-        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL);
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
         // BAR: 99 * 2 = 198
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
@@ -731,7 +718,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
          // BAR: 101 * 20 = 2020
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
@@ -747,7 +734,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L));
         rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
 
-        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL);
+        result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
         assertEquals(result.size(), 2);
         // BAR: 101 * 20 = 2020
         assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
@@ -759,7 +746,77 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertEquals(result.get(1).getRate().compareTo(new BigDecimal("100.0")), 0);
     }
 
-    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, TierBlockPolicy tierBlockPolicy, UsageDetailMode usageDetailMode) throws CatalogApiException {
+
+
+    @Test(groups = "fast")
+    public void testMultipleItemsAndTiersWithExistingItemsAllTiers() throws CatalogApiException, IOException {
+
+        //
+        // Let's assume we were already billed on the previous period
+        //
+        // FOO : 10 (tier 1) + 40 (tier 2) = 50
+        final UsageConsumableInArrearTierUnitDetail existingFooUsageTier1 = new UsageConsumableInArrearTierUnitDetail(1, "FOO", BigDecimal.ONE, 1, 10, new BigDecimal("10.00"));
+        final UsageConsumableInArrearTierUnitDetail existingFooUsageTier2 = new UsageConsumableInArrearTierUnitDetail(2, "FOO", BigDecimal.TEN, 1, 40, new BigDecimal("400.00"));
+        // BAR : 10 (tier 1) + 40 (tier 2)
+        final UsageConsumableInArrearTierUnitDetail existingBarUsageTier1 = new UsageConsumableInArrearTierUnitDetail(1, "BAR", new BigDecimal("2.00"), 1, 80, new BigDecimal("160.00"));
+
+        final List<UsageConsumableInArrearTierUnitDetail> existingUsage = ImmutableList.of(existingFooUsageTier1, existingFooUsageTier2, existingBarUsageTier1);
+
+        final UsageConsumableInArrearDetail tmp = new UsageConsumableInArrearDetail(existingUsage);
+
+        final String existingUsageJson = tmp.toJson(objectMapper);
+
+        //
+        // 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
+
+        final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
+        existingItems.add(ii1);
+
+
+        List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, existingItems);
+        assertEquals(result.size(), 1);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("3140.00")), 0, String.format("%s != 3140.0", result.get(0).getAmount()));
+
+        List<UsageConsumableInArrearTierUnitDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageConsumableInArrearTierUnitDetail>>() {});
+        // BAR item detail
+        assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+        assertEquals(itemDetails.get(0).getTier(), 1);
+        assertEquals(itemDetails.get(0).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(0).getQuantity().intValue(), 20);
+        assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.00")), 0);
+        assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("40.00")), 0);
+
+        assertEquals(itemDetails.get(1).getTierUnit(), "BAR");
+        assertEquals(itemDetails.get(1).getTier(), 2);
+        assertEquals(itemDetails.get(1).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(1).getQuantity().intValue(), 100);
+        assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("20.00")), 0);
+        assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("2000.00")), 0);
+
+        // FOO item detail
+        assertEquals(itemDetails.get(2).getTierUnit(), "FOO");
+        assertEquals(itemDetails.get(2).getTier(), 2);
+        assertEquals(itemDetails.get(2).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(2).getQuantity().intValue(), 10);
+        assertEquals(itemDetails.get(2).getTierPrice().compareTo(new BigDecimal("10.00")), 0);
+        assertEquals(itemDetails.get(2).getAmount().compareTo(new BigDecimal("100.00")), 0);
+
+        assertEquals(itemDetails.get(3).getTierUnit(), "FOO");
+        assertEquals(itemDetails.get(3).getTier(), 3);
+        assertEquals(itemDetails.get(3).getTierBlockSize(), 1);
+        assertEquals(itemDetails.get(3).getQuantity().intValue(), 10);
+        assertEquals(itemDetails.get(3).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+        assertEquals(itemDetails.get(3).getAmount().compareTo(new BigDecimal("1000.00")), 0);
+    }
+
+
+
+    private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, TierBlockPolicy tierBlockPolicy, UsageDetailMode usageDetailMode, final List<InvoiceItem> existingItems) throws CatalogApiException {
 
         final LocalDate startDate = new LocalDate(2014, 03, 20);
         final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -785,7 +842,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
 
         final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
 
-        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of());
+        final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
         final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
         final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
             @Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
index f5c5c60..7a205c1 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
@@ -22,7 +22,6 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.DefaultInternationalPrice;
@@ -45,11 +44,10 @@ import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.usage.RawUsage;
 import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
 import org.mockito.Mockito;
 import org.testng.annotations.BeforeClass;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
 
     protected int BCD;