/*
* Copyright 2010-2013 Ning, Inc.
* 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.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.catalog.DefaultTier;
import org.killbill.billing.catalog.DefaultTieredBlock;
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.Usage;
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.UsageInArrearItemsAndNextNotificationDate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUsage;
import org.killbill.billing.usage.api.svcs.DefaultRawUsage;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearBase {
// Only works if the RolledUpUsage have at least one
private static final Ordering<RolledUpUsage> TEST_ROLLED_UP_FIRST_USAGE_ORDERING = Ordering.natural()
.onResultOf(new Function<RolledUpUsage, Comparable>() {
@Override
public Comparable apply(final RolledUpUsage ru) {
return ru.getRolledUpUnits().get(0).getUnitType();
}
});
@BeforeClass(groups = "fast")
protected void beforeClass() throws Exception {
super.beforeClass();
}
@BeforeMethod(groups = "fast")
public void beforeMethod() {
super.beforeMethod();
}
@Test(groups = "fast")
public void testComputeToBeBilledUsage() {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate endDate = new LocalDate(2014, 04, 20);
final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 1000, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(block);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
final LocalDate targetDate = startDate.plusDays(1);
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(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);
existingUsage.add(ii1);
final UsageInvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
existingUsage.add(ii2);
// Will be ignored as is starts one day earlier.
final UsageInvoiceItem ii3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate.minusDays(1), endDate, BigDecimal.TEN, currency);
existingUsage.add(ii3);
// Will be ignored as it is for a different udsage section
final UsageInvoiceItem ii4 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, "other", startDate, endDate, BigDecimal.TEN, currency);
existingUsage.add(ii4);
// Will be ignored because non usage item
final FixedPriceInvoiceItem ii5 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, BigDecimal.TEN, currency);
existingUsage.add(ii5);
final BigDecimal result = intervalConsumableInArrear.computeBilledUsage(intervalConsumableInArrear.getBilledItems(startDate, endDate, existingUsage));
assertEquals(result.compareTo(BigDecimal.TEN.add(BigDecimal.TEN)), 0);
}
@Test(groups = "fast")
public void testComputeBilledUsage() throws CatalogApiException {
final DefaultTieredBlock block1 = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
final DefaultTier tier1 = createDefaultTierWithBlocks(block1);
final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 1000, 100, BigDecimal.ONE);
final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, tier1, tier2);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
BillingPeriod.MONTHLY,
Collections.<Usage>emptyList())
);
final BigDecimal result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L));
// 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15
assertEquals(result, new BigDecimal("15"));
}
@Test(groups = "fast")
public void testComputeMissingItems() throws CatalogApiException {
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));
final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(block);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
final LocalDate targetDate = endDate;
final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(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);
invoiceItems.add(ii1);
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 List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
assertEquals(rawResults.size(), 4);
final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
@Override
public boolean apply(final InvoiceItem input) {
return input.getAmount().compareTo(BigDecimal.ZERO) > 0;
}
}));
// Invoiced for 1 BTC and used 130 + 271 = 401 => 5 blocks => 5 BTC so remaining piece should be 4 BTC
assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("4.0")), 0, String.format("%s != 4.0", result.get(0).getAmount()));
assertEquals(result.get(0).getCurrency(), Currency.BTC);
assertEquals(result.get(0).getAccountId(), accountId);
assertEquals(result.get(0).getBundleId(), bundleId);
assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
assertEquals(result.get(0).getPlanName(), planName);
assertEquals(result.get(0).getPhaseName(), phaseName);
assertEquals(result.get(0).getUsageName(), usage.getName());
assertTrue(result.get(0).getStartDate().compareTo(startDate) == 0);
assertTrue(result.get(0).getEndDate().compareTo(firstBCDDate) == 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()));
assertEquals(result.get(1).getCurrency(), Currency.BTC);
assertEquals(result.get(1).getAccountId(), accountId);
assertEquals(result.get(1).getBundleId(), bundleId);
assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
assertEquals(result.get(1).getPlanName(), planName);
assertEquals(result.get(1).getPhaseName(), phaseName);
assertEquals(result.get(1).getUsageName(), usage.getName());
assertTrue(result.get(1).getStartDate().compareTo(firstBCDDate) == 0);
assertTrue(result.get(1).getEndDate().compareTo(endDate) == 0);
}
@Test(groups = "fast")
public void testGetRolledUpUsage() {
final DefaultTieredBlock tieredBlock1 = createDefaultTieredBlock("unit", 100, 1000, BigDecimal.ONE);
final DefaultTieredBlock tieredBlock2 = createDefaultTieredBlock("unit2", 10, 1000, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(tieredBlock1, tieredBlock2);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
final LocalDate t0 = new LocalDate(2015, 03, BCD);
final BillingEvent eventT0 = createMockBillingEvent(t0.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate t1 = new LocalDate(2015, 04, BCD);
final BillingEvent eventT1 = createMockBillingEvent(t1.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate t2 = new LocalDate(2015, 05, BCD);
final BillingEvent eventT2 = createMockBillingEvent(t2.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate t3 = new LocalDate(2015, 06, BCD);
final BillingEvent eventT3 = createMockBillingEvent(t3.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final LocalDate targetDate = t3;
// Prev t0
final RawUsage raw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 01), "unit", 12L);
// t0 - t1
final RawUsage raw2 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 15), "unit", 6L);
final RawUsage raw3 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 25), "unit", 4L);
// t1 - t2 nothing
// t2 - t3
final RawUsage raw4 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 15), "unit", 13L);
final RawUsage oraw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 21), "unit2", 21L);
final RawUsage raw5 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 31), "unit", 7L);
// after t3
final RawUsage raw6 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 06, 15), "unit", 100L);
final List<RawUsage> rawUsage = ImmutableList.of(raw1, raw2, raw3, raw4, oraw1, raw5, raw6);
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsage, targetDate, true, eventT0, eventT1, eventT2, eventT3);
final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage();
Assert.assertEquals(unsortedRolledUpUsage.size(), 2);
final List<RolledUpUsage> rolledUpUsage = TEST_ROLLED_UP_FIRST_USAGE_ORDERING.sortedCopy(unsortedRolledUpUsage);
Assert.assertEquals(rolledUpUsage.get(0).getStart().compareTo(t0), 0);
Assert.assertEquals(rolledUpUsage.get(0).getEnd().compareTo(t1), 0);
Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().size(),1);
Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().get(0).getUnitType(), "unit");
Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().get(0).getAmount(), new Long(10L));
Assert.assertEquals(rolledUpUsage.get(1).getStart().compareTo(t2), 0);
Assert.assertEquals(rolledUpUsage.get(1).getEnd().compareTo(t3), 0);
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().size(),2);
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(0).getUnitType(), "unit");
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(0).getAmount(), new Long(20L));
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(1).getUnitType(), "unit2");
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(1).getAmount(), new Long(21L));
}
@Test(groups = "fast", description="See https://github.com/killbill/killbill/issues/706")
public void testWithRawUsageStartDateAfterEndDate() throws CatalogApiException {
final LocalDate startDate = new LocalDate(2014, 10, 16);
final LocalDate endDate = startDate;
final LocalDate targetDate = endDate;
final LocalDate rawUsageStartDate = new LocalDate(2015, 10, 16);
final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
rawUsages.add(new DefaultRawUsage(subscriptionId, startDate, "unit", 130L));
final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(block);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
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, internalCallContext);
intervalConsumableInArrear.addBillingEvent(event1);
intervalConsumableInArrear.addBillingEvent(event2);
final ContiguousIntervalUsageInArrear res = intervalConsumableInArrear.build(true);
assertEquals(res.getTransitionTimes().size(), 0);
}
}