Search in sources :

Example 91 with InvoiceItem

use of org.killbill.billing.invoice.api.InvoiceItem in project killbill by killbill.

the class FixedAndRecurringInvoiceItemGenerator method processFixedBillingEvents.

@VisibleForTesting
void processFixedBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List<InvoiceItem> proposedItems, final InternalCallContext internalCallContext) throws InvoiceApiException {
    if (events.isEmpty()) {
        return;
    }
    InvoiceItem prevItem = null;
    // Pretty-print the generated invoice items from the junction events
    final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger = new InvoiceItemGeneratorLogger(invoiceId, accountId, "fixed", log);
    final Iterator<BillingEvent> eventIt = events.iterator();
    while (eventIt.hasNext()) {
        final BillingEvent thisEvent = eventIt.next();
        final InvoiceItem currentFixedPriceItem = generateFixedPriceItem(invoiceId, accountId, thisEvent, targetDate, currency, invoiceItemGeneratorLogger, internalCallContext);
        if (!isSameDayAndSameSubscription(prevItem, thisEvent, internalCallContext) && prevItem != null) {
            proposedItems.add(prevItem);
        }
        prevItem = currentFixedPriceItem;
    }
    // The last one if not null can always be inserted as there is nothing after to cancel it off.
    if (prevItem != null) {
        proposedItems.add(prevItem);
    }
    invoiceItemGeneratorLogger.logItems();
}
Also used : FixedPriceInvoiceItem(org.killbill.billing.invoice.model.FixedPriceInvoiceItem) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) BillingEvent(org.killbill.billing.junction.BillingEvent) VisibleForTesting(com.google.common.annotations.VisibleForTesting)

Example 92 with InvoiceItem

use of org.killbill.billing.invoice.api.InvoiceItem in project killbill by killbill.

the class FixedAndRecurringInvoiceItemGenerator method processRecurringEvent.

// Turn a set of events into a list of invoice items. Note that the dates on the invoice items will be rounded (granularity of a day)
private List<InvoiceItem> processRecurringEvent(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, @Nullable final BillingEvent nextEvent, final LocalDate targetDate, final Currency currency, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger, final BillingMode billingMode, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate, final InternalCallContext internalCallContext) throws InvoiceApiException {
    try {
        final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
        // For FIXEDTERM phases we need to stop when the specified duration has been reached
        final LocalDate maxEndDate = thisEvent.getPlanPhase().getPhaseType() == PhaseType.FIXEDTERM ? thisEvent.getPlanPhase().getDuration().addToLocalDate(internalCallContext.toLocalDate(thisEvent.getEffectiveDate())) : null;
        // Handle recurring items
        final BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
        if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
            final LocalDate startDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate());
            if (!startDate.isAfter(targetDate)) {
                final LocalDate endDate = (nextEvent == null) ? null : internalCallContext.toLocalDate(nextEvent.getEffectiveDate());
                final int billCycleDayLocal = thisEvent.getBillCycleDayLocal();
                final RecurringInvoiceItemDataWithNextBillingCycleDate itemDataWithNextBillingCycleDate;
                try {
                    itemDataWithNextBillingCycleDate = generateInvoiceItemData(startDate, endDate, targetDate, billCycleDayLocal, billingPeriod, billingMode);
                } catch (final InvalidDateSequenceException e) {
                    throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
                }
                for (final RecurringInvoiceItemData itemDatum : itemDataWithNextBillingCycleDate.getItemData()) {
                    // Stop if there a maxEndDate and we have reached it
                    if (maxEndDate != null && maxEndDate.compareTo(itemDatum.getEndDate()) < 0) {
                        break;
                    }
                    final BigDecimal rate = thisEvent.getRecurringPrice(internalCallContext.toUTCDateTime(itemDatum.getStartDate()));
                    if (rate != null) {
                        final BigDecimal amount = KillBillMoney.of(itemDatum.getNumberOfCycles().multiply(rate), currency);
                        final RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(), thisEvent.getSubscription().getId(), thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(), itemDatum.getStartDate(), itemDatum.getEndDate(), amount, rate, currency);
                        items.add(recurringItem);
                    }
                }
                updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate);
            }
        }
        // For debugging purposes
        invoiceItemGeneratorLogger.append(thisEvent, items);
        return items;
    } catch (final CatalogApiException e) {
        throw new InvoiceApiException(e);
    }
}
Also used : FixedPriceInvoiceItem(org.killbill.billing.invoice.model.FixedPriceInvoiceItem) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) BillingPeriod(org.killbill.billing.catalog.api.BillingPeriod) InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod(org.killbill.billing.invoice.generator.InvoiceDateUtils.calculateProRationBeforeFirstBillingPeriod) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) ArrayList(java.util.ArrayList) RecurringInvoiceItemData(org.killbill.billing.invoice.model.RecurringInvoiceItemData) LocalDate(org.joda.time.LocalDate) BigDecimal(java.math.BigDecimal) InvalidDateSequenceException(org.killbill.billing.invoice.model.InvalidDateSequenceException) RecurringInvoiceItemDataWithNextBillingCycleDate(org.killbill.billing.invoice.model.RecurringInvoiceItemDataWithNextBillingCycleDate) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException)

Example 93 with InvoiceItem

use of org.killbill.billing.invoice.api.InvoiceItem in project killbill by killbill.

the class FixedAndRecurringInvoiceItemGenerator method safetyBounds.

@VisibleForTesting
void safetyBounds(final Iterable<InvoiceItem> resultingItems, final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription, final InternalTenantContext internalCallContext) throws InvoiceApiException {
    // See https://github.com/killbill/killbill/issues/664
    if (config.isSanitySafetyBoundEnabled(internalCallContext)) {
        final Map<UUID, Multimap<LocalDate, InvoiceItem>> fixedItemsPerDateAndSubscription = new HashMap<UUID, Multimap<LocalDate, InvoiceItem>>();
        final Map<UUID, Multimap<Range<LocalDate>, InvoiceItem>> recurringItemsPerServicePeriodAndSubscription = new HashMap<UUID, Multimap<Range<LocalDate>, InvoiceItem>>();
        for (final InvoiceItem resultingItem : resultingItems) {
            if (resultingItem.getInvoiceItemType() == InvoiceItemType.FIXED) {
                if (fixedItemsPerDateAndSubscription.get(resultingItem.getSubscriptionId()) == null) {
                    fixedItemsPerDateAndSubscription.put(resultingItem.getSubscriptionId(), LinkedListMultimap.<LocalDate, InvoiceItem>create());
                }
                fixedItemsPerDateAndSubscription.get(resultingItem.getSubscriptionId()).put(resultingItem.getStartDate(), resultingItem);
                final Collection<InvoiceItem> resultingInvoiceItems = fixedItemsPerDateAndSubscription.get(resultingItem.getSubscriptionId()).get(resultingItem.getStartDate());
                if (resultingInvoiceItems.size() > 1) {
                    throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED Multiple FIXED items for subscriptionId='%s', startDate='%s', resultingItems=%s", resultingItem.getSubscriptionId(), resultingItem.getStartDate(), resultingInvoiceItems));
                }
            } else if (resultingItem.getInvoiceItemType() == InvoiceItemType.RECURRING) {
                if (recurringItemsPerServicePeriodAndSubscription.get(resultingItem.getSubscriptionId()) == null) {
                    recurringItemsPerServicePeriodAndSubscription.put(resultingItem.getSubscriptionId(), LinkedListMultimap.<Range<LocalDate>, InvoiceItem>create());
                }
                final Range<LocalDate> interval = Range.<LocalDate>closedOpen(resultingItem.getStartDate(), resultingItem.getEndDate());
                recurringItemsPerServicePeriodAndSubscription.get(resultingItem.getSubscriptionId()).put(interval, resultingItem);
                final Collection<InvoiceItem> resultingInvoiceItems = recurringItemsPerServicePeriodAndSubscription.get(resultingItem.getSubscriptionId()).get(interval);
                if (resultingInvoiceItems.size() > 1) {
                    throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED Multiple RECURRING items for subscriptionId='%s', startDate='%s', endDate='%s', resultingItems=%s", resultingItem.getSubscriptionId(), resultingItem.getStartDate(), resultingItem.getEndDate(), resultingInvoiceItems));
                }
            }
        }
    }
    // Trigger an exception if we create too many invoice items for a subscription on a given day
    if (config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext) == -1) {
        // Safety bound disabled
        return;
    }
    for (final InvoiceItem invoiceItem : resultingItems) {
        if (invoiceItem.getSubscriptionId() != null) {
            final LocalDate resultingItemCreationDay = trackInvoiceItemCreatedDay(invoiceItem, createdItemsPerDayPerSubscription, internalCallContext);
            final Collection<LocalDate> creationDaysForSubscription = createdItemsPerDayPerSubscription.get(invoiceItem.getSubscriptionId());
            int i = 0;
            for (final LocalDate creationDayForSubscription : creationDaysForSubscription) {
                if (creationDayForSubscription.compareTo(resultingItemCreationDay) == 0) {
                    i++;
                    if (i > config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext)) {
                        // Proposed items have already been logged
                        throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED subscriptionId='%s', resultingItem=%s", invoiceItem.getSubscriptionId(), invoiceItem));
                    }
                }
            }
        }
    }
}
Also used : FixedPriceInvoiceItem(org.killbill.billing.invoice.model.FixedPriceInvoiceItem) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) HashMap(java.util.HashMap) Range(com.google.common.collect.Range) LocalDate(org.joda.time.LocalDate) Multimap(com.google.common.collect.Multimap) LinkedListMultimap(com.google.common.collect.LinkedListMultimap) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) Collection(java.util.Collection) UUID(java.util.UUID) VisibleForTesting(com.google.common.annotations.VisibleForTesting)

Example 94 with InvoiceItem

use of org.killbill.billing.invoice.api.InvoiceItem in project killbill by killbill.

the class FixedAndRecurringInvoiceItemGenerator method generateItems.

public List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List<Invoice> existingInvoices, final LocalDate targetDate, final Currency targetCurrency, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate, final InternalCallContext internalCallContext) throws InvoiceApiException {
    final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription = LinkedListMultimap.<UUID, LocalDate>create();
    final AccountItemTree accountItemTree = new AccountItemTree(account.getId(), invoiceId);
    if (existingInvoices != null) {
        for (final Invoice invoice : existingInvoices) {
            for (final InvoiceItem item : invoice.getInvoiceItems()) {
                if (// Always include migration invoices, credits, external charges etc.
                item.getSubscriptionId() == null || !eventSet.getSubscriptionIdsWithAutoInvoiceOff().contains(item.getSubscriptionId())) {
                    //don't add items with auto_invoice_off tag
                    accountItemTree.addExistingItem(item);
                    trackInvoiceItemCreatedDay(item, createdItemsPerDayPerSubscription, internalCallContext);
                }
            }
        }
    }
    // Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time
    final List<InvoiceItem> proposedItems = new ArrayList<InvoiceItem>();
    processRecurringBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems, perSubscriptionFutureNotificationDate, existingInvoices, internalCallContext);
    processFixedBillingEvents(invoiceId, account.getId(), eventSet, targetDate, targetCurrency, proposedItems, internalCallContext);
    try {
        accountItemTree.mergeWithProposedItems(proposedItems);
    } catch (final IllegalStateException e) {
        // Proposed items have already been logged
        throw new InvoiceApiException(e, ErrorCode.UNEXPECTED_ERROR, String.format("ILLEGAL INVOICING STATE accountItemTree=%s", accountItemTree.toString()));
    }
    final List<InvoiceItem> resultingItems = accountItemTree.getResultingItemList();
    safetyBounds(resultingItems, createdItemsPerDayPerSubscription, internalCallContext);
    return resultingItems;
}
Also used : InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) AccountItemTree(org.killbill.billing.invoice.tree.AccountItemTree) Invoice(org.killbill.billing.invoice.api.Invoice) FixedPriceInvoiceItem(org.killbill.billing.invoice.model.FixedPriceInvoiceItem) RecurringInvoiceItem(org.killbill.billing.invoice.model.RecurringInvoiceItem) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) ArrayList(java.util.ArrayList) UUID(java.util.UUID) LocalDate(org.joda.time.LocalDate)

Example 95 with InvoiceItem

use of org.killbill.billing.invoice.api.InvoiceItem in project killbill by killbill.

the class UsageInvoiceItemGenerator method generateItems.

@Override
public List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet, @Nullable final List<Invoice> existingInvoices, final LocalDate targetDate, final Currency targetCurrency, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates, final InternalCallContext internalCallContext) throws InvoiceApiException {
    final Map<UUID, List<InvoiceItem>> perSubscriptionInArrearUsageItems = extractPerSubscriptionExistingInArrearUsageItems(eventSet.getUsages(), existingInvoices);
    try {
        // Pretty-print the generated invoice items from the junction events
        final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger = new InvoiceItemGeneratorLogger(invoiceId, account.getId(), "usage", log);
        final LocalDate minBillingEventDate = getMinBillingEventDate(eventSet, internalCallContext);
        final List<InvoiceItem> items = Lists.newArrayList();
        final Iterator<BillingEvent> events = eventSet.iterator();
        RawUsageOptimizerResult rawUsageOptimizerResult = null;
        List<BillingEvent> curEvents = Lists.newArrayList();
        UUID curSubscriptionId = null;
        while (events.hasNext()) {
            final BillingEvent event = events.next();
            // Skip events that are posterior to the targetDate
            final LocalDate eventLocalEffectiveDate = internalCallContext.toLocalDate(event.getEffectiveDate());
            if (eventLocalEffectiveDate.isAfter(targetDate)) {
                continue;
            }
            // Optimize to do the usage query only once after we know there are indeed some usage items
            if (rawUsageOptimizerResult == null && Iterables.any(event.getUsages(), new Predicate<Usage>() {

                @Override
                public boolean apply(@Nullable final Usage input) {
                    return input.getBillingMode() == BillingMode.IN_ARREAR;
                }
            })) {
                rawUsageOptimizerResult = rawUsageOptimizer.getInArrearUsage(minBillingEventDate, targetDate, Iterables.concat(perSubscriptionInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext);
            }
            // None of the billing events report any usage IN_ARREAR sections
            if (rawUsageOptimizerResult == null) {
                continue;
            }
            final UUID subscriptionId = event.getSubscription().getId();
            if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) {
                final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate(), internalCallContext);
                final List<InvoiceItem> usageInArrearItems = perSubscriptionInArrearUsageItems.get(curSubscriptionId);
                final SubscriptionUsageInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionUsageInArrear.computeMissingUsageInvoiceItems(usageInArrearItems != null ? usageInArrearItems : ImmutableList.<InvoiceItem>of(), invoiceItemGeneratorLogger);
                final List<InvoiceItem> newInArrearUsageItems = subscriptionResult.getInvoiceItems();
                items.addAll(newInArrearUsageItems);
                updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
                curEvents = Lists.newArrayList();
            }
            curSubscriptionId = subscriptionId;
            curEvents.add(event);
        }
        if (curSubscriptionId != null) {
            final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate(), internalCallContext);
            final List<InvoiceItem> usageInArrearItems = perSubscriptionInArrearUsageItems.get(curSubscriptionId);
            final SubscriptionUsageInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionUsageInArrear.computeMissingUsageInvoiceItems(usageInArrearItems != null ? usageInArrearItems : ImmutableList.<InvoiceItem>of(), invoiceItemGeneratorLogger);
            final List<InvoiceItem> newInArrearUsageItems = subscriptionResult.getInvoiceItems();
            items.addAll(newInArrearUsageItems);
            updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
        }
        invoiceItemGeneratorLogger.logItems();
        return items;
    } catch (final CatalogApiException e) {
        throw new InvoiceApiException(e);
    }
}
Also used : Usage(org.killbill.billing.catalog.api.Usage) InvoiceItem(org.killbill.billing.invoice.api.InvoiceItem) SubscriptionUsageInArrearItemsAndNextNotificationDate(org.killbill.billing.invoice.usage.SubscriptionUsageInArrear.SubscriptionUsageInArrearItemsAndNextNotificationDate) LocalDate(org.joda.time.LocalDate) Predicate(com.google.common.base.Predicate) InvoiceApiException(org.killbill.billing.invoice.api.InvoiceApiException) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) RawUsageOptimizerResult(org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult) ImmutableList(com.google.common.collect.ImmutableList) LinkedList(java.util.LinkedList) List(java.util.List) BillingEvent(org.killbill.billing.junction.BillingEvent) SubscriptionUsageInArrear(org.killbill.billing.invoice.usage.SubscriptionUsageInArrear) UUID(java.util.UUID) Nullable(javax.annotation.Nullable)

Aggregations

InvoiceItem (org.killbill.billing.invoice.api.InvoiceItem)168 LocalDate (org.joda.time.LocalDate)118 Test (org.testng.annotations.Test)109 BigDecimal (java.math.BigDecimal)103 FixedPriceInvoiceItem (org.killbill.billing.invoice.model.FixedPriceInvoiceItem)97 RecurringInvoiceItem (org.killbill.billing.invoice.model.RecurringInvoiceItem)92 ItemAdjInvoiceItem (org.killbill.billing.invoice.model.ItemAdjInvoiceItem)79 RepairAdjInvoiceItem (org.killbill.billing.invoice.model.RepairAdjInvoiceItem)76 UUID (java.util.UUID)68 Invoice (org.killbill.billing.invoice.api.Invoice)57 DefaultInvoice (org.killbill.billing.invoice.model.DefaultInvoice)36 ExternalChargeInvoiceItem (org.killbill.billing.invoice.model.ExternalChargeInvoiceItem)32 BillingEvent (org.killbill.billing.junction.BillingEvent)27 InvoiceApiException (org.killbill.billing.invoice.api.InvoiceApiException)25 ArrayList (java.util.ArrayList)23 DateTime (org.joda.time.DateTime)23 MockPlan (org.killbill.billing.catalog.MockPlan)23 MockPlanPhase (org.killbill.billing.catalog.MockPlanPhase)23 Plan (org.killbill.billing.catalog.api.Plan)22 PlanPhase (org.killbill.billing.catalog.api.PlanPhase)21