killbill-memoizeit
Changes
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java 135(+135 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java 248(+248 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java 407(+75 -332)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java 69(+69 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearDetail.java 74(+74 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitDetail.java 73(+73 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearDetail.java 29(+29 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java 58(+58 -0)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java 236(+149 -87)
Details
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
new file mode 100644
index 0000000..3652965
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Limit;
+import org.killbill.billing.catalog.api.Tier;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
+import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearTier;
+
+public class ContiguousIntervalCapacityUsageInArrear extends ContiguousIntervalUsageInArrear {
+
+ private static final Joiner joiner = Joiner.on(", ");
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ public ContiguousIntervalCapacityUsageInArrear(final Usage usage,
+ final UUID accountId,
+ final UUID invoiceId,
+ final List<RawUsage> rawSubscriptionUsage,
+ final LocalDate targetDate,
+ final LocalDate rawUsageStartDate,
+ final UsageDetailMode usageDetailMode,
+ final InternalTenantContext internalTenantContext) {
+ super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ }
+
+
+
+ @Override
+ protected void populateResults(final LocalDate startDate, final LocalDate endDate, final Iterable<InvoiceItem> billedItems, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearDetail toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) {
+ // Compute final amount by subtracting amount that was already billed.
+ if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
+ final BigDecimal amountToBill = toBeBilledUsage.subtract(billedUsage);
+
+ if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
+ final String itemDetails = areAllBilledItemsWithDetails ? toBeBilledUsageDetails.toJson(objectMapper) : null;
+ final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
+ result.add(item);
+ }
+ }
+
+ }
+
+
+ @Override
+ protected UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+ return computeToBeBilledCapacityInArrear(rolledUpUnits);
+ }
+
+ private Limit getTierLimit(final Tier tier, final String unitType) {
+ for (final Limit cur : tier.getLimits()) {
+ if (cur.getUnit().getName().equals(unitType)) {
+ return cur;
+ }
+ }
+ Preconditions.checkState(false, "Could not find unit type " + unitType + " in usage tier ");
+ return null;
+ }
+
+ @VisibleForTesting
+ 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<UsageInArrearTierUnitDetail> toBeBilledDetails = Lists.newLinkedList();
+ for (final Tier cur : tiers) {
+ tierNum++;
+ boolean complies = true;
+ for (final RolledUpUnit ro : roUnits) {
+ final Limit tierLimit = getTierLimit(cur, ro.getUnitType());
+ // We ignore the min and only look at the max Limit as the tiers should be contiguous.
+ // Specifying a -1 value for last max tier will make the validation works
+ if (tierLimit.getMax() != (double) -1 && ro.getAmount().doubleValue() > tierLimit.getMax()) {
+ complies = false;
+ } else {
+ if (!perUnitTypeDetailTierLevel.contains(ro.getUnitType())) {
+ toBeBilledDetails.add(new UsageInArrearTierUnitDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), ro.getAmount().intValue()));
+ perUnitTypeDetailTierLevel.add(ro.getUnitType());
+ }
+ }
+ }
+ if (complies) {
+ return new UsageCapacityInArrearDetail(toBeBilledDetails, cur.getRecurringPrice().getPrice(getCurrency()));
+ }
+ }
+ // Probably invalid catalog config
+ joiner.join(roUnits);
+ Preconditions.checkState(false, "Could not find tier for usage " + usage.getName() + "matching with data = " + joiner.join(roUnits));
+ return null;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
new file mode 100644
index 0000000..7ef86ff
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.TieredBlock;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.UsageInvoiceItem;
+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.usage.RawUsage;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearTieredBlocks;
+
+public class ContiguousIntervalConsumableUsageInArrear extends ContiguousIntervalUsageInArrear {
+
+ private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalConsumableUsageInArrear.class);
+
+ public ContiguousIntervalConsumableUsageInArrear(final Usage usage,
+ final UUID accountId,
+ final UUID invoiceId,
+ final List<RawUsage> rawSubscriptionUsage,
+ final LocalDate targetDate,
+ final LocalDate rawUsageStartDate,
+ final UsageDetailMode usageDetailMode,
+ final InternalTenantContext internalTenantContext) {
+ super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ }
+
+ @Override
+ protected void populateResults(final LocalDate startDate, final LocalDate endDate, final Iterable<InvoiceItem> billedItems, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearDetail toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) {
+ // Compute final amount by subtracting amount that was already billed.
+ if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
+ // In the case past invoice items showed the details (areAllBilledItemsWithDetails=true), billed usage has already been taken into account
+ // as it part of the reconciliation logic, so no need to subtract it here
+ final BigDecimal amountToBill = areAllBilledItemsWithDetails ? toBeBilledUsage : toBeBilledUsage.subtract(billedUsage);
+
+ if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
+ if (UsageDetailMode.DETAIL == usageDetailMode) {
+ for (UsageConsumableInArrearTierUnitDetail toBeBilledUsageDetail : ((UsageConsumableInArrearDetail) toBeBilledUsageDetails).getTierDetails()) {
+ final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), null);
+ result.add(item);
+ }
+ } else {
+ final String itemDetails = toBeBilledUsageDetails.toJson(objectMapper);
+ final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
+ result.add(item);
+ }
+ }
+ }
+
+ }
+
+ @Override
+ protected UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+
+ 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();
+ }
+
+ 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;
+ }
+ final List<UsageConsumableInArrearTierUnitDetail> previousUsage = previousUnitsUsage.containsKey(cur.getUnitType()) ? previousUnitsUsage.get(cur.getUnitType()) : ImmutableList.<UsageConsumableInArrearTierUnitDetail>of();
+
+ usageConsumableInArrearTierUnitDetails.addAll(computeToBeBilledConsumableInArrear(cur, previousUsage, areAllBilledItemsWithDetails));
+ }
+ final UsageInArrearDetail toBeBilledUsageDetails = new UsageConsumableInArrearDetail(usageConsumableInArrearTierUnitDetails);
+ return toBeBilledUsageDetails;
+ }
+
+ private List<UsageConsumableInArrearTierUnitDetail> getUsageConsumableInArrearDetailForUnitType(final Iterable<InvoiceItem> billedItems, final String unitType) {
+
+ final List<UsageConsumableInArrearTierUnitDetail> result = new ArrayList<UsageConsumableInArrearTierUnitDetail>();
+ for (final InvoiceItem bi : billedItems) {
+
+ final UsageConsumableInArrearDetail usageDetail = fromJson(bi.getItemDetails());
+ for (final UsageConsumableInArrearTierUnitDetail curDetail : usageDetail.getTierDetails()) {
+ if (curDetail.getTierUnit().equals(unitType)) {
+ result.add(curDetail);
+ }
+ }
+ }
+ return result;
+ }
+
+ @VisibleForTesting
+ 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, previousUsage, roUnit.getAmount());
+ case TOP_TIER:
+ 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<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);
+ int nbUsedTierBlocks;
+ if (tmp > tieredBlock.getMax()) {
+ nbUsedTierBlocks = tieredBlock.getMax().intValue();
+ remainingUnits -= tieredBlock.getMax() * blockTierSize;
+ } else {
+ nbUsedTierBlocks = tmp;
+ remainingUnits = 0;
+ }
+
+ if (nbUsedTierBlocks > 0) {
+ 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, previousUsageQuantity));
+ }
+ nbUsedTierBlocks = nbUsedTierBlocks - previousUsageQuantity;
+ }
+ if (nbUsedTierBlocks > 0) {
+ toBeBilledDetails.add(new UsageConsumableInArrearTierUnitDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), blockTierSize, nbUsedTierBlocks));
+ }
+ }
+ }
+ return toBeBilledDetails;
+ }
+
+ UsageConsumableInArrearTierUnitDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitDetail> previousUsage, final Long units, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+
+ int remainingUnits = units.intValue();
+
+ // By default last last tierBlock
+ TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
+ int targetTierNum = tieredBlocks.size();
+ int tierNum = 0;
+ // Loop through all tier block
+ for (final TieredBlock tieredBlock : tieredBlocks) {
+
+ tierNum++;
+ final int blockTierSize = tieredBlock.getSize().intValue();
+ final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
+ if (tmp > tieredBlock.getMax()) {
+ remainingUnits -= tieredBlock.getMax() * blockTierSize;
+ } else {
+ targetBlock = tieredBlock;
+ targetTierNum = tierNum;
+ break;
+ }
+ }
+ final int lastBlockTierSize = targetBlock.getSize().intValue();
+ final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
+
+ return new UsageConsumableInArrearTierUnitDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), targetBlock.getSize().intValue(), nbBlocks);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ContiguousIntervalConsumableUsageInArrear{");
+ sb.append("transitionTimes=").append(transitionTimes);
+ sb.append(", billingEvents=").append(billingEvents);
+ sb.append(", rawSubscriptionUsage=").append(rawSubscriptionUsage);
+ sb.append(", rawUsageStartDate=").append(rawUsageStartDate);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static UsageConsumableInArrearDetail fromJson(String itemDetails) {
+ UsageConsumableInArrearDetail result = null;
+ if (itemDetails != null) {
+ try {
+ result = objectMapper.readValue(itemDetails, new TypeReference<UsageConsumableInArrearDetail>() {});
+ } catch (IOException e) {
+ Preconditions.checkState(false, e.getMessage());
+ }
+ }
+ return result;
+ }
+
+}
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 b49d684..429ed96 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
@@ -20,8 +20,8 @@ package org.killbill.billing.invoice.usage;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -32,32 +32,34 @@ import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.Limit;
import org.killbill.billing.catalog.api.Tier;
-import org.killbill.billing.catalog.api.TieredBlock;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
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.UsageConsumableInArrearTierUnitDetail;
+import org.killbill.billing.invoice.usage.details.UsageInArrearDetail;
+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;
@@ -68,33 +70,30 @@ import com.google.common.collect.Lists;
import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearTier;
import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearUnitTypes;
-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.
*/
-public class ContiguousIntervalUsageInArrear {
+public abstract class ContiguousIntervalUsageInArrear {
private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalUsageInArrear.class);
- private final List<LocalDate> transitionTimes;
- private final List<BillingEvent> billingEvents;
-
- private final Usage usage;
- private final Set<String> unitTypes;
- private final List<RawUsage> rawSubscriptionUsage;
- private final LocalDate targetDate;
- private final UUID accountId;
- private final UUID invoiceId;
- private final AtomicBoolean isBuilt;
- private final LocalDate rawUsageStartDate;
- private final InternalTenantContext internalTenantContext;
- private final UsageDetailMode usageDetailMode;
- private static final ObjectMapper objectMapper = new ObjectMapper();
+ protected final List<LocalDate> transitionTimes;
+ protected final List<BillingEvent> billingEvents;
+
+ protected final Usage usage;
+ protected final Set<String> unitTypes;
+ protected final List<RawUsage> rawSubscriptionUsage;
+ protected final LocalDate targetDate;
+ protected final UUID accountId;
+ protected final UUID invoiceId;
+ protected final AtomicBoolean isBuilt;
+ protected final LocalDate rawUsageStartDate;
+ protected final InternalTenantContext internalTenantContext;
+ protected final UsageDetailMode usageDetailMode;
+ protected static final ObjectMapper objectMapper = new ObjectMapper();
public ContiguousIntervalUsageInArrear(final Usage usage,
final UUID accountId,
@@ -171,7 +170,7 @@ public class ContiguousIntervalUsageInArrear {
* @param existingUsage existing on disk usage items for the subscription
* @throws CatalogApiException
*/
- public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException {
+ public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException, InvoiceApiException {
Preconditions.checkState(isBuilt.get());
@@ -194,56 +193,56 @@ public class ContiguousIntervalUsageInArrear {
}
final List<RolledUpUsage> allUsage = getRolledUpUsage();
+ // Each RolledUpUsage 'ru' is for a specific time period and across all units
for (final RolledUpUsage ru : allUsage) {
- List<UsageInArrearDetail> toBeBilledUsageDetails = Lists.newLinkedList();
- BigDecimal toBeBilledUsage = BigDecimal.ZERO;
- if (usage.getUsageType() == UsageType.CAPACITY) {
- toBeBilledUsageDetails.addAll(computeToBeBilledCapacityInArrear(ru.getRolledUpUnits()));
- } else /* UsageType.CONSUMABLE */{
-
- // Compute total price amount that should be billed for that period of time (and usage section) across unitTypes.
- for (final RolledUpUnit cur : ru.getRolledUpUnits()) {
- if (!unitTypes.contains(cur.getUnitType())) {
- log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
- continue;
- }
+ //
+ // Previously billed items:
+ //
+ // 1. Retrieves current price amount billed for that period of time (and usage section)
+ final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStart(), ru.getEnd(), existingUsage);
+ // 2. Verify whether previously built items have the item_details section
+ final boolean areAllBilledItemsWithDetails = areAllBilledItemsWithDetails(billedItems);
+ // 3. Computes total billed usage amount
+ final BigDecimal billedUsage = computeBilledUsage(billedItems);
- toBeBilledUsageDetails.addAll(computeToBeBilledConsumableInArrear(cur));
- }
+ final List<RolledUpUnit> rolledUpUnits = ru.getRolledUpUnits();
- }
- toBeBilledUsage = toBeBilledForUnit(toBeBilledUsageDetails);
- // Retrieves current price amount billed for that period of time (and usage section)
- final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStart(), ru.getEnd(), existingUsage);
- final BigDecimal billedUsage = computeBilledUsage(billedItems);
- // Compare the two and add the missing piece if required.
- if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
- toBeBilledUsageDetails = reconcileExistedBilledWithToBeBilled(billedItems, toBeBilledUsageDetails);
- final BigDecimal amountToBill = toBeBilledForUnit(toBeBilledUsageDetails);
-
- if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
- if (UsageDetailMode.DETAIL == usageDetailMode && usage.getUsageType() == UsageType.CONSUMABLE){
- for (UsageInArrearDetail toBeBilledUsageDetail : toBeBilledUsageDetails){
- 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);
- result.add(item);
- }
- } else {
- final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
- getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, null, getCurrency(),null, toJson(toBeBilledUsageDetails));
- result.add(item);
- }
- }
+ final UsageInArrearDetail toBeBilledUsageDetails = getToBeBilledUsageDetails(rolledUpUnits, billedItems, areAllBilledItemsWithDetails);
+ final BigDecimal toBeBilledUsage = toBeBilledUsageDetails.getAmount();
+
+ final int billedUsageDelta = billedUsage.compareTo(toBeBilledUsage);
+ // We built more in the past than what we find now, data usage vanished?
+ if (billedUsageDelta > 0) {
+ throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR,
+ String.format("ILLEGAL INVOICING STATE: Usage period start='%s', end='%s', previously billed amount='%.2f', new proposed amount='%.2f'",
+ ru.getStart(), ru.getEnd(), billedUsage, toBeBilledUsage));
+ // Something remains to be billed
+ } else if (billedUsageDelta < 0) {
+ populateResults(ru.getStart(), ru.getEnd(), billedItems, billedUsage, toBeBilledUsage, toBeBilledUsageDetails, areAllBilledItemsWithDetails, result);
}
}
-
final LocalDate nextNotificationdate = computeNextNotificationDate();
return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationdate);
}
+ protected abstract void populateResults(final LocalDate startDate, final LocalDate endDate, final Iterable<InvoiceItem> billedItems, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearDetail toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result);
+
+
+ protected abstract UsageInArrearDetail getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException;
+
+ private boolean areAllBilledItemsWithDetails(final Iterable<InvoiceItem> billedItems) {
+ boolean atLeastOneItemWithoutDetails = Iterables.any(billedItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getItemDetails() == null || input.getItemDetails().isEmpty();
+ }
+ });
+ return !atLeastOneItemWithoutDetails;
+ }
+
private LocalDate computeNextNotificationDate() {
LocalDate result = null;
final Iterator<BillingEvent> eventIt = billingEvents.iterator();
@@ -310,7 +309,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;
}
@@ -331,7 +330,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);
}
}
@@ -369,7 +368,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)) {
@@ -381,122 +379,6 @@ public class ContiguousIntervalUsageInArrear {
}
/**
- * @param roUnits the list of rolled up units for the period
- * @return the price amount that should be billed for that period/unitType
- * @throws CatalogApiException
- */
- @VisibleForTesting
- List<UsageInArrearDetail> computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits) throws CatalogApiException {
- Preconditions.checkState(isBuilt.get());
-
- final List<Tier> tiers = getCapacityInArrearTier(usage);
- int tierNum = 0;
- for (final Tier cur : tiers) {
- tierNum++;
- List<UsageInArrearDetail> toBeBilledDetails = Lists.newLinkedList();
- boolean complies = true;
- for (final RolledUpUnit ro : roUnits) {
- final Limit tierLimit = getTierLimit(cur, ro.getUnitType());
- // We ignore the min and only look at the max Limit as the tiers should be contiguous.
- // Specifying a -1 value for last max tier will make the validation works
- if (tierLimit.getMax() != (double) -1 && ro.getAmount().doubleValue() > tierLimit.getMax()) {
- complies = false;
- break;
- }
- toBeBilledDetails.add(new UsageInArrearDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), ro.getAmount().intValue(), BigDecimal.ZERO, BigDecimal.ZERO, ""));
-
- }
- if (complies) {
- toBeBilledDetails.get(toBeBilledDetails.size() - 1).setAmount(cur.getRecurringPrice().getPrice(getCurrency()));
- return toBeBilledDetails;
- }
- }
- // 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));
- return null;
- }
-
- /**
- * @param roUnit the rolled up unit for the period
- * @return the price amount that should be billed for that period/unitType
- * @throws CatalogApiException
- */
- @VisibleForTesting
- List<UsageInArrearDetail> computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit) 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());
- case TOP_TIER:
- return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, roUnit.getAmount()));
- default:
- throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy());
- }
- }
-
-
- List<UsageInArrearDetail> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final Long units) throws CatalogApiException {
-
- List<UsageInArrearDetail> toBeBilledDetails = Lists.newLinkedList();
- BigDecimal result = BigDecimal.ZERO;
- int remainingUnits = units.intValue();
- int tierNum = 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;
- if (tmp > tieredBlock.getMax()) {
- nbUsedTierBlocks = tieredBlock.getMax().intValue();
- remainingUnits -= tieredBlock.getMax() * blockTierSize;
- } else {
- nbUsedTierBlocks = tmp;
- remainingUnits = 0;
- }
-
- if (nbUsedTierBlocks > 0) {
- toBeBilledDetails.add(new UsageInArrearDetail(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), nbUsedTierBlocks));
- }
- }
- return toBeBilledDetails;
- }
-
- UsageInArrearDetail computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final Long units) throws CatalogApiException {
-
- int remainingUnits = units.intValue();
-
- // By default last last tierBlock
- TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
- int targetTierNum = tieredBlocks.size();
- int tierNum = 0;
- // Loop through all tier block
- for (final TieredBlock tieredBlock : tieredBlocks) {
-
- tierNum++;
- final int blockTierSize = tieredBlock.getSize().intValue();
- final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
- if (tmp > tieredBlock.getMax()) {
- remainingUnits -= tieredBlock.getMax() * blockTierSize;
- } else {
- targetBlock = tieredBlock;
- targetTierNum = tierNum;
- break;
- }
- }
- final int lastBlockTierSize = targetBlock.getSize().intValue();
- final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
-
- return new UsageInArrearDetail(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), 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)
*/
@@ -512,10 +394,10 @@ public class ContiguousIntervalUsageInArrear {
return billedAmount;
}
- Iterable<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
+ List<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
Preconditions.checkState(isBuilt.get());
- return Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
+ final Iterable<InvoiceItem> filteredResult = Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
@Override
public boolean apply(final InvoiceItem input) {
if (input.getInvoiceItemType() != InvoiceItemType.USAGE) {
@@ -528,6 +410,7 @@ public class ContiguousIntervalUsageInArrear {
usageInput.getEndDate().compareTo(endDate) <= 0;
}
});
+ return ImmutableList.copyOf(filteredResult);
}
@VisibleForTesting
@@ -599,155 +482,15 @@ 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(Iterable<InvoiceItem> billedItems, List<UsageInArrearDetail> toBeBilledUsageInArrearDetails) {
- for (final InvoiceItem bi : billedItems) {
- List<UsageInArrearDetail> billedUsageItemDetails = fromJson(bi.getItemDetails());
-
- if (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, bi.getId().toString()));
- }
- }
- } else {
- toBeBilledUsageInArrearDetails.get(0).setAmount(toBeBilledUsageInArrearDetails.get(0).getAmount().subtract(bi.getAmount()));
- toBeBilledUsageInArrearDetails.get(0).setQuantity(toBeBilledUsageInArrearDetails.get(0).getQuantity() - (bi.getQuantity() == null ? 0 : bi.getQuantity()));
- toBeBilledUsageInArrearDetails.get(0).setExistingUsageAmount(toBeBilledUsageInArrearDetails.get(0).getExistingUsageAmount().add(bi.getAmount()));
- toBeBilledUsageInArrearDetails.get(0).setReference(toBeBilledUsageInArrearDetails.get(0).getReference().concat(":").concat(bi.getId().toString()));
- }
- }
-
- return toBeBilledUsageInArrearDetails;
- }
-
- private static final String toJson(List<UsageInArrearDetail> toBeBilledUsageInArrearDetails) {
- String result = null;
- if (toBeBilledUsageInArrearDetails != null && toBeBilledUsageInArrearDetails.size() > 0){
- try {
- result = objectMapper.writeValueAsString(toBeBilledUsageInArrearDetails);
- } catch (JsonProcessingException e) {
- Preconditions.checkState(false, e.getMessage());
- }
- }
- return result;
- }
-
- private static final List<UsageInArrearDetail> fromJson(String itemDetails){
- List<UsageInArrearDetail> toBeBilledUsageInArrearDetails = null;
- if (itemDetails != null){
- try {
- toBeBilledUsageInArrearDetails = objectMapper.readValue(itemDetails, new TypeReference<List<UsageInArrearDetail>>() {});
- } catch (IOException e) {
- Preconditions.checkState(false, e.getMessage());
+ 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 toBeBilledUsageInArrearDetails;
- }
-
- public static class UsageInArrearDetail {
-
- private final int tier;
- private final String tierUnit;
- private final BigDecimal tierPrice;
- private Integer quantity;
- private String reference;
- private BigDecimal existingUsageAmount;
- private BigDecimal amount;
-
- public UsageInArrearDetail(BigDecimal tierPrice, Integer quantity, BigDecimal existingUsageAmount, String reference) {
- this(0, null, tierPrice, quantity, existingUsageAmount, BigDecimal.ZERO, reference);
- }
-
- public UsageInArrearDetail(int tier, String tierUnit, BigDecimal tierPrice, Integer quantity) {
- this(tier, tierUnit, tierPrice, quantity, tierPrice.multiply(new BigDecimal(quantity)), BigDecimal.ZERO, "");
- }
-
- @JsonCreator
- public UsageInArrearDetail(@JsonProperty("tier") int tier, @JsonProperty("tierUnit") String tierUnit,
- @JsonProperty("tierPrice") BigDecimal tierPrice, @JsonProperty("quantity") Integer quantity,
- @JsonProperty("amount") BigDecimal amount, @JsonProperty("existingUsageAmount") BigDecimal existingUsageAmount,
- @JsonProperty("reference") String reference) {
- this.tier = tier;
- this.tierUnit = tierUnit;
- this.tierPrice = tierPrice;
- this.quantity = quantity;
- this.amount = amount;
- this.existingUsageAmount = existingUsageAmount;
- 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 getExistingUsageAmount() {
- return existingUsageAmount;
- }
-
- public String getReference() {
- return reference;
- }
-
- public void setExistingUsageAmount(BigDecimal existingUsageAmount) {
- this.existingUsageAmount = existingUsageAmount;
- }
-
- 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(List<UsageInArrearDetail> billedUsageItemDetails) {
- List<UsageInArrearDetail> unreconciledUsage = Lists.newLinkedList();
- for (UsageInArrearDetail billedUsageDetail : billedUsageItemDetails) {
- if (tierUnit.equals(billedUsageDetail.tierUnit)) {
- existingUsageAmount = billedUsageDetail.getAmount().abs();
- quantity = quantity - billedUsageDetail.getQuantity();
- amount = amount.subtract(existingUsageAmount);
- } else {
- unreconciledUsage.add(billedUsageDetail);
- }
- }
-
- return unreconciledUsage;
+ return result;
}
}
}
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..9298f99
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearDetail.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+ }
+
+ @Override
+ public String toJson(final ObjectMapper objectMapper) {
+
+
+ String result = null;
+ if (tierDetails != null && tierDetails.size() > 0) {
+ try {
+ result = objectMapper.writeValueAsString(this);
+ } 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..927836a
--- /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(this);
+ } 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/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/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
index b104127..93bbd42 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
@@ -33,6 +33,7 @@ import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.generator.InvoiceItemGenerator.InvoiceItemGeneratorLogger;
import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
@@ -113,7 +114,7 @@ public class SubscriptionUsageInArrear {
* @param existingUsage the existing on disk usage items.
* @throws CatalogApiException
*/
- public SubscriptionUsageInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger) throws CatalogApiException {
+ public SubscriptionUsageInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger) throws CatalogApiException, InvoiceApiException {
final SubscriptionUsageInArrearItemsAndNextNotificationDate result = new SubscriptionUsageInArrearItemsAndNextNotificationDate();
final List<ContiguousIntervalUsageInArrear> billingEventTransitionTimePeriods = computeInArrearUsageInterval();
for (final ContiguousIntervalUsageInArrear usageInterval : billingEventTransitionTimePeriods) {
@@ -154,7 +155,10 @@ public class SubscriptionUsageInArrear {
// Add inflight usage interval if non existent
ContiguousIntervalUsageInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
if (existingInterval == null) {
- existingInterval = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ existingInterval = usage.getUsageType() == UsageType.CAPACITY ?
+ new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext) :
+ new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+
inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
}
// Add billing event for that usage interval
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
index 69baa60..5e0e026 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
@@ -16,10 +16,8 @@
package org.killbill.billing.invoice.usage;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.killbill.billing.catalog.api.BillingMode;
@@ -40,13 +38,19 @@ public class UsageUtils {
Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CONSUMABLE);
Preconditions.checkArgument(usage.getTiers().length > 0);
+
final List<TieredBlock> result = Lists.newLinkedList();
for (Tier tier : usage.getTiers()) {
+ boolean found = false;
for (TieredBlock tierBlock : tier.getTieredBlocks()) {
if (tierBlock.getUnit().getName().equals(unitType)) {
result.add(tierBlock);
+ found = true;
+ break;
}
}
+ // We expect this method to return an ordered list of TieredBlock, each for each tier.
+ Preconditions.checkState(found, "Catalog issue in usage section '%s': Missing tierBlock definition for unit '%s'", usage.getName(), unitType);
}
return result;
}
@@ -65,7 +69,6 @@ public class UsageUtils {
return result;
}
-
public static List<Tier> getCapacityInArrearTier(final Usage usage) {
Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CAPACITY);
@@ -73,7 +76,6 @@ public class UsageUtils {
return ImmutableList.copyOf(usage.getTiers());
}
-
public static Set<String> getCapacityInArrearUnitTypes(final Usage usage) {
Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CAPACITY);
@@ -88,5 +90,4 @@ public class UsageUtils {
return result;
}
-
}
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 14cbf50..ee8131f 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,19 @@ 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.invoice.api.InvoiceApiException;
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.UsageInArrearTierUnitDetail;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUnit;
@@ -85,11 +85,11 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final LocalDate targetDate = startDate.plusDays(1);
- final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
final List<InvoiceItem> existingUsage = Lists.newArrayList();
final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
@@ -117,7 +117,6 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
@Test(groups = "fast")
public void testComputeBilledUsage() throws CatalogApiException {
-
final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
final DefaultUnit unit3 = new DefaultUnit().setName("unit3");
@@ -138,73 +137,83 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final DefaultLimit limit3_3 = new DefaultLimit().setUnit(unit3).setMax((double) -1).setMin((double) -1);
final DefaultTier tier3 = createDefaultTierWithLimits(new BigDecimal("30.0"), limit3_1, limit3_2, limit3_3);
-
final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier1, tier2, tier3);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ 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)));
- 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)));
- assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+ 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)));
- assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
-
+ 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)));
- assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("20.0")) == 0);
+ 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)));
- assertTrue(intervalCapacityInArrear.toBeBilledForUnit(result).compareTo(new BigDecimal("30.0")) == 0);
+ assertTrue(result.getAmount().compareTo(new BigDecimal("30.0")) == 0);
}
@Test(groups = "fast")
- public void testComputeMissingItems() throws CatalogApiException {
+ public void testComputeMissingItems() throws CatalogApiException, InvoiceApiException {
final LocalDate startDate = new LocalDate(2014, 03, 20);
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 DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit);
+ final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
+ final DefaultLimit limit2 = new DefaultLimit().setUnit(unit2).setMax((double) -1);
- final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
+ 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 event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2);
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, event1, event2);
final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
@@ -213,7 +222,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
invoiceItems.add(ii2);
- final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
+ final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
assertEquals(rawResults.size(), 4);
@@ -224,7 +233,6 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
}
}));
-
assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9.0")), 0, String.format("%s != 9.0", result.get(0).getAmount()));
assertEquals(result.get(0).getCurrency(), Currency.BTC);
assertEquals(result.get(0).getAccountId(), accountId);
@@ -249,82 +257,137 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
}
@Test(groups = "fast")
- public void testComputeMissingItemsAggregateMode() throws CatalogApiException, IOException {
-
- testComputeMissingItemsByMode(UsageDetailMode.AGGREGATE);
+ public void testMultipleItemsAndTiersAggregateMode() throws CatalogApiException, IOException, InvoiceApiException {
+ testMultipleItemsAndTiers(UsageDetailMode.AGGREGATE);
}
@Test(groups = "fast")
- public void testComputeMissingItemsDetailMode() throws CatalogApiException, IOException {
-
- testComputeMissingItemsByMode(UsageDetailMode.DETAIL);
+ public void testMultipleItemsAndTiersDetailMode() throws CatalogApiException, IOException, InvoiceApiException {
+ testMultipleItemsAndTiers(UsageDetailMode.DETAIL);
}
- private void testComputeMissingItemsByMode(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException {
+ private void testMultipleItemsAndTiers(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException, InvoiceApiException {
// 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()));
+ 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>>() {});
+ UsageCapacityInArrearDetail itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearDetail>() {});
+ assertEquals(itemDetails.getAmount().compareTo(BigDecimal.ONE), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+
+ List<UsageInArrearTierUnitDetail> itemUnitDetails = itemDetails.getTierDetails();
// BAR item detail
- assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
- assertEquals(itemDetails.get(0).getTier(), 1);
- assertEquals(itemDetails.get(0).getQuantity().intValue(), 99);
- assertEquals(itemDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(0).getTier(), 1);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 99);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
// FOO item detail
- assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
- assertEquals(itemDetails.get(1).getTier(), 1);
- assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
- assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(1).getTier(), 1);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 5);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
// Case 2
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()));
+ 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<UsageCapacityInArrearDetail>() {});
+ assertEquals(itemDetails.getAmount().compareTo(BigDecimal.TEN), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+ itemUnitDetails = itemDetails.getTierDetails();
- itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
- // BAR item detail
- assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
- assertEquals(itemDetails.get(0).getTier(), 2);
- assertEquals(itemDetails.get(0).getQuantity().intValue(), 101);
- assertEquals(itemDetails.get(0).getTierPrice().compareTo(BigDecimal.TEN), 0);
// FOO item detail
- assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
- assertEquals(itemDetails.get(1).getTier(), 2);
- assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
- assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.TEN), 0);
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(0).getTier(), 1);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 5);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // BAR item detail
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(1).getTier(), 2);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 101);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(BigDecimal.TEN), 0);
// Case 3
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()));
+ 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<UsageCapacityInArrearDetail>() {});
+ assertEquals(itemDetails.getAmount().compareTo(new BigDecimal("100.0")), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+ itemUnitDetails = itemDetails.getTierDetails();
- itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
// BAR item detail
- assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
- assertEquals(itemDetails.get(0).getTier(), 3);
- assertEquals(itemDetails.get(0).getQuantity().intValue(), 101);
- assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(0).getTier(), 2);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 101);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(new BigDecimal("10.0")), 0);
+
// FOO item detail
- assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
- assertEquals(itemDetails.get(1).getTier(), 3);
- assertEquals(itemDetails.get(1).getQuantity().intValue(), 75);
- assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(1).getTier(), 3);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 75);
+ assertEquals(itemUnitDetails.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, InvoiceApiException {
+
+ // let's assume we have some existing usage
+ final UsageInArrearTierUnitDetail existingFooUsageTier1 = new UsageInArrearTierUnitDetail(1, "FOO", BigDecimal.ONE, 9);
+ final UsageInArrearTierUnitDetail existingBarUsageTier2 = new UsageInArrearTierUnitDetail(2, "BAR", BigDecimal.TEN, 200);
+
+
+ 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()));
+
+ UsageCapacityInArrearDetail itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearDetail>() {});
+ assertEquals(itemDetails.getAmount().compareTo(new BigDecimal("100.00")), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+
+ List<UsageInArrearTierUnitDetail> itemUnitDetails = itemDetails.getTierDetails();
+
+ // BAR item detail
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(0).getTier(), 2);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 200);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.TEN), 0);
+ // FOO item detail
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(1).getTier(), 3);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 60);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+ }
+
+ private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, UsageDetailMode usageDetailMode, List<InvoiceItem> existingItems) throws CatalogApiException, InvoiceApiException {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -349,12 +412,12 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final LocalDate targetDate = endDate;
- final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ 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());
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
- final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of());
+ final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
@Override
@@ -363,7 +426,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
}
}));
- for (InvoiceItem item: result) {
+ for (InvoiceItem item : result) {
assertEquals(item.getCurrency(), Currency.BTC);
assertEquals(item.getAccountId(), accountId);
assertEquals(item.getBundleId(), bundleId);
@@ -378,5 +441,4 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
return result;
}
-
}
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 3c63a05..9d15f4d 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,14 @@ 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.InvoiceApiException;
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 +52,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;
@@ -64,6 +61,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearBase {
@@ -143,13 +141,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
- List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+ 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"));
@@ -171,13 +169,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
- List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L));
+ 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
@@ -203,7 +201,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
@@ -211,23 +209,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));
+ 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));
+ 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));
+ 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));
+ 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"));
@@ -250,13 +248,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
- List<UsageInArrearDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+ List<UsageConsumableInArrearTierUnitDetail> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitDetail>of(), true);
assertEquals(result.size(), 1);
// 111 = 111 * 0.5 =
@@ -267,7 +265,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
@Test(groups = "fast")
- public void testComputeMissingItems() throws CatalogApiException, IOException {
+ public void testComputeMissingItems() throws CatalogApiException, IOException, InvoiceApiException {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -321,11 +319,12 @@ 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>>() {});
-
+ assertNotNull(result.get(0).getItemDetails());
+ UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
assertEquals(itemDetails.size(), 1);
- assertEquals(itemDetails.get(0).getAmount().compareTo(result.get(0).getAmount()),0);
+ // Because we did not have the details before, the new details don't take into account the
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("5.0")), 0);
// 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()));
@@ -338,11 +337,13 @@ 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);
+ assertNotNull(result.get(1).getItemDetails());
+
+ usageDetail = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ itemDetails = usageDetail.getTierDetails();
+ assertEquals(itemDetails.size(), 1);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("2.0")), 0);
- // check item detail
- List<UsageInArrearDetail> itemDetails2 = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
- assertEquals(itemDetails2.size(), 1);
- assertEquals(itemDetails2.get(0).getAmount().compareTo(result.get(1).getAmount()),0);
}
@Test(groups = "fast")
@@ -436,7 +437,11 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final BillingEvent event2 = createMockBillingEvent(new LocalDate(2014, 10, 16).toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext);
+
+ final ContiguousIntervalUsageInArrear intervalConsumableInArrear = usage.getUsageType() == UsageType.CAPACITY ?
+ new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext) :
+ new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext);
+
intervalConsumableInArrear.addBillingEvent(event1);
intervalConsumableInArrear.addBillingEvent(event2);
@@ -453,32 +458,34 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
- List<UsageInArrearDetail> results = Lists.newArrayList();
- results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L)));
- results.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L)));
+ 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")
- public void testComputeMissingItemsAggregateModeAllTier() throws CatalogApiException, IOException {
+ public void testComputeMissingItemsAggregateModeAllTier() throws CatalogApiException, IOException, InvoiceApiException {
// 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, 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);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")), 0);
+
+ UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
- List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
// BAR: 99 * 2 = 198
assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
assertEquals(itemDetails.get(0).getTier(), 1);
@@ -497,11 +504,13 @@ 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);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("225")), 0);
+
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ itemDetails = usageDetail.getTierDetails();
- itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
// BAR: 100 * 2 = 200
assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
assertEquals(itemDetails.get(0).getTier(), 1);
@@ -526,11 +535,12 @@ 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);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2230")), 0);
- itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ itemDetails = usageDetail.getTierDetails();
// BAR: 100 * 2 = 200
assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
assertEquals(itemDetails.get(0).getTier(), 1);
@@ -564,14 +574,14 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
}
@Test(groups = "fast")
- public void testComputeMissingItemsDetailModeAllTier() throws CatalogApiException, IOException {
+ public void testComputeMissingItemsDetailModeAllTier() throws CatalogApiException, IOException, InvoiceApiException {
// 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, 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);
@@ -587,7 +597,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);
@@ -607,7 +617,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);
@@ -632,18 +642,19 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
}
@Test(groups = "fast")
- public void testComputeMissingItemsAggregateModeTopTier() throws CatalogApiException, IOException {
+ public void testComputeMissingItemsAggregateModeTopTier() throws CatalogApiException, IOException, InvoiceApiException {
// 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, 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);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")), 0);
- List<UsageInArrearDetail> itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+ UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
// BAR: 99 * 2 = 198
assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
assertEquals(itemDetails.get(0).getTier(), 1);
@@ -662,11 +673,13 @@ 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);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2025")), 0);
+
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ itemDetails = usageDetail.getTierDetails();
- itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
// BAR: 101 * 20 = 2020
assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
assertEquals(itemDetails.get(0).getTier(), 2);
@@ -685,11 +698,12 @@ 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);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9620")), 0);
- itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<List<UsageInArrearDetail>>() {});
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ itemDetails = usageDetail.getTierDetails();
// BAR: 101 * 20 = 2020
assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
assertEquals(itemDetails.get(0).getTier(), 2);
@@ -705,14 +719,14 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
}
@Test(groups = "fast")
- public void testComputeMissingItemsDetailModeTopTier() throws CatalogApiException, IOException {
+ public void testComputeMissingItemsDetailModeTopTier() throws CatalogApiException, IOException, InvoiceApiException {
// 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, 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);
@@ -728,9 +742,9 @@ 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
+ // BAR: 101 * 20 = 2020
assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
assertEquals(result.get(0).getQuantity().intValue(), 101);
assertEquals(result.get(0).getRate().compareTo(new BigDecimal("20.0")), 0);
@@ -744,7 +758,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);
@@ -756,7 +770,82 @@ 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, InvoiceApiException {
+
+ //
+ // 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()));
+
+ UsageConsumableInArrearDetail usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearDetail>() {});
+ List<UsageConsumableInArrearTierUnitDetail> itemDetails = usageDetail.getTierDetails();
+
+ // 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);
+ }
+
+
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersWithExistingItemsTopTier() throws CatalogApiException, IOException {
+ // TODO + code
+ }
+
+ private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, TierBlockPolicy tierBlockPolicy, UsageDetailMode usageDetailMode, final List<InvoiceItem> existingItems) throws CatalogApiException, InvoiceApiException {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -782,7 +871,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..42c1709 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;
@@ -80,12 +78,27 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
objectMapper = new ObjectMapper();
}
- protected ContiguousIntervalUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+
+ protected ContiguousIntervalCapacityUsageInArrear createContiguousIntervalCapacityInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+ return createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, closedInterval, usageDetailMode, events);
+ }
+
+ protected ContiguousIntervalCapacityUsageInArrear createContiguousIntervalCapacityInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
+ for (final BillingEvent event : events) {
+ intervalCapacityInArrear.addBillingEvent(event);
+ }
+ intervalCapacityInArrear.build(closedInterval);
+ return intervalCapacityInArrear;
+ }
+
+
+ protected ContiguousIntervalConsumableUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
return createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, closedInterval, usageDetailMode, events);
}
- protected ContiguousIntervalUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
+ protected ContiguousIntervalConsumableUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
for (final BillingEvent event : events) {
intervalConsumableInArrear.addBillingEvent(event);
}