use of org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId in project killbill by killbill.
the class ContiguousIntervalUsageInArrear method getRolledUpUsage.
@VisibleForTesting
RolledUpUnitsWithTracking getRolledUpUsage() throws InvoiceApiException {
final List<RolledUpUsageWithMetadata> result = new ArrayList<RolledUpUsageWithMetadata>();
final Set<TrackingRecordId> trackingIds = new HashSet<>();
final Iterator<RawUsageRecord> rawUsageIterator = rawSubscriptionUsage.iterator();
if (!rawUsageIterator.hasNext()) {
return new RolledUpUnitsWithTracking(getEmptyRolledUpUsage(), ImmutableSet.of());
}
//
// Skip all items before our first transition date
//
// 'prevRawUsage' keeps track of first unconsumed raw usage element
RawUsageRecord prevRawUsage = null;
while (rawUsageIterator.hasNext()) {
final RawUsageRecord curRawUsage = rawUsageIterator.next();
if (curRawUsage.getDate().compareTo(transitionTimes.get(0).getDate()) >= 0) {
prevRawUsage = curRawUsage;
break;
}
}
// Optimize path where all raw usage items are outside or our transitionTimes range
if (prevRawUsage == null || prevRawUsage.getDate().compareTo(transitionTimes.get(transitionTimes.size() - 1).getDate()) > 0) {
return new RolledUpUnitsWithTracking(getEmptyRolledUpUsage(), ImmutableSet.of());
}
//
// Loop through each interval [prevDate, curDate) and consume as many rawSubscriptionUsage elements within that range
// to create one RolledUpUsage per interval. The first loop will be used to set the 'prevDate'.
//
LocalDate prevDate = null;
DateTime prevCatalogEffectiveDate = null;
for (int i = 0; i < transitionTimes.size(); i++) {
final TransitionTime curTransition = transitionTimes.get(i);
final LocalDate curDate = curTransition.getDate();
if (prevDate != null) {
// Allocate and initialize new perRangeUnitToAmount for this interval and populate with rawSubscriptionUsage items
final Map<String, Long> perRangeUnitToAmount = new HashMap<String, Long>();
for (String unitType : unitTypes) {
perRangeUnitToAmount.put(unitType, 0L);
}
// Start consuming prevRawUsage element if it exists and falls into the range
if (prevRawUsage != null) {
// Special treatment for final cancellation to make sure we include this usage point as part of the interval and bill for usage reported on cancellation date
final boolean isUsageForCancellationDay = i == transitionTimes.size() - 1 && curTransition.getTargetBillingEvent().getTransitionType() == SubscriptionBaseTransitionType.CANCEL && curDate.compareTo(prevRawUsage.getDate()) == 0;
if (prevRawUsage.getDate().compareTo(prevDate) >= 0 && (prevRawUsage.getDate().compareTo(curDate) < 0 || isUsageForCancellationDay)) {
final Long currentAmount = perRangeUnitToAmount.get(prevRawUsage.getUnitType());
final Long updatedAmount = computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
perRangeUnitToAmount.put(prevRawUsage.getUnitType(), updatedAmount);
trackingIds.add(new TrackingRecordId(prevRawUsage.getTrackingId(), invoiceId, prevRawUsage.getSubscriptionId(), prevRawUsage.getUnitType(), prevRawUsage.getDate()));
prevRawUsage = null;
}
}
//
if (prevRawUsage == null) {
while (rawUsageIterator.hasNext()) {
final RawUsageRecord curRawUsage = rawUsageIterator.next();
final boolean isUsageForCancellationDay = i == transitionTimes.size() - 1 && curTransition.getTargetBillingEvent().getTransitionType() == SubscriptionBaseTransitionType.CANCEL && curDate.compareTo(curRawUsage.getDate()) == 0;
// Special treatment for final cancellation to make sure we include this usage point as part of the interval and bill for usage reported on cancellation date
if (!isUsageForCancellationDay && curRawUsage.getDate().compareTo(curDate) >= 0) {
prevRawUsage = curRawUsage;
break;
}
final Long currentAmount = perRangeUnitToAmount.get(curRawUsage.getUnitType());
final Long updatedAmount = computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
perRangeUnitToAmount.put(curRawUsage.getUnitType(), updatedAmount);
trackingIds.add(new TrackingRecordId(curRawUsage.getTrackingId(), invoiceId, curRawUsage.getSubscriptionId(), curRawUsage.getUnitType(), curRawUsage.getDate()));
}
}
// If we did find some usage for that date range, let's populate the result
if (!perRangeUnitToAmount.isEmpty()) {
final List<RolledUpUnit> rolledUpUnits = new ArrayList<RolledUpUnit>(perRangeUnitToAmount.size());
for (final Entry<String, Long> entry : perRangeUnitToAmount.entrySet()) {
final String unitType = entry.getKey();
// Sanity check: https://github.com/killbill/killbill/issues/1275
if (!allSeenUnitTypes.get(curTransition.getTargetBillingEvent()).contains(unitType)) {
// We have found some reported usage with a unit type that hasn't been handled by any ContiguousIntervalUsageInArrear
if (invoiceConfig.shouldParkAccountsWithUnknownUsage(internalTenantContext)) {
throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("ILLEGAL INVOICING STATE: unit type %s is not defined in the catalog effective Date %s for subscription %s)", unitType, curTransition.getTargetBillingEvent().getCatalogEffectiveDate(), curTransition.getTargetBillingEvent().getSubscriptionId()));
} else {
log.warn("Ignoring unit type {} for subscription {} (not defined in the catalog effective Date {})", unitType, curTransition.getTargetBillingEvent().getSubscriptionId(), curTransition.getTargetBillingEvent().getCatalogEffectiveDate());
// Make sure to remove the associated tracking ids
final Iterator<TrackingRecordId> itr = trackingIds.iterator();
while (itr.hasNext()) {
final TrackingRecordId t = itr.next();
if (unitType.equals(t.getUnitType())) {
itr.remove();
}
}
}
} else if (unitTypes.contains(unitType)) {
// Other usage type not for us -- safely ignore
rolledUpUnits.add(new DefaultRolledUpUnit(unitType, entry.getValue()));
}
}
result.add(new DefaultRolledUpUsageWithMetadata(getSubscriptionId(), prevDate, curDate, rolledUpUnits, prevCatalogEffectiveDate));
}
}
prevDate = curDate;
prevCatalogEffectiveDate = curTransition.getTargetBillingEvent().getCatalogEffectiveDate();
}
return new RolledUpUnitsWithTracking(result, trackingIds);
}
use of org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId in project killbill by killbill.
the class TestContiguousIntervalCapacityInArrear method testComputeMissingItems.
@Test(groups = "fast")
public void testComputeMissingItems() throws Exception {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
final LocalDate endDate = new LocalDate(2014, 05, 15);
final List<RawUsageRecord> rawUsageRecords = new ArrayList<RawUsageRecord>();
//
// First period: startDate - firstBCDDate
//
// 2 items for unit1
rawUsageRecords.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit1", 130L, "tracking-1"));
rawUsageRecords.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit1", 271L, "tracking-2"));
// 1 items for unit2
rawUsageRecords.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 24), "unit2", 10L, "tracking-1"));
//
// Second period: firstBCDDate - endDate
//
// 1 items unit1
rawUsageRecords.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit1", 199L, "tracking-4"));
// 1 items unit2
rawUsageRecords.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit2", 20L, "tracking-5"));
final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
final DefaultLimit limit1 = new DefaultLimit().setUnit(unit1).setMax((double) -1);
final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
final DefaultLimit limit2 = new DefaultLimit().setUnit(unit2).setMax((double) -1);
final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, 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(), catalogEffectiveDate);
final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList(), catalogEffectiveDate);
final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsageRecords, targetDate, true, event1, event2);
final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), null, startDate, firstBCDDate, BigDecimal.ONE, currency);
invoiceItems.add(ii1);
final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usage.getName(), null, firstBCDDate, endDate, BigDecimal.ONE, currency);
invoiceItems.add(ii2);
final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
final List<InvoiceItem> result = usageResult.getInvoiceItems();
assertEquals(result.size(), 2);
final Set<TrackingRecordId> trackingIds = usageResult.getTrackingIds();
checkTrackingIds(rawUsageRecords, usageResult.getTrackingIds());
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.USD);
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);
assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("9.0")), 0, String.format("%s != 9.0", result.get(0).getAmount()));
assertEquals(result.get(1).getCurrency(), Currency.USD);
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);
}
Aggregations