protected BillingEvent createNewReenableEvent(final DateTime odEventTime, final BillingEvent previousEvent, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException { // All fields are populated with the event state from before the blocking period, for invoice to resume invoicing final int billCycleDay = previousEvent.getBillCycleDayLocal(); final SubscriptionBase subscription = previousEvent.getSubscription(); final DateTime effectiveDate = odEventTime; final PlanPhase planPhase = previousEvent.getPlanPhase(); final BigDecimal fixedPrice = previousEvent.getFixedPrice(); final Plan plan = previousEvent.getPlan(); final Currency currency = previousEvent.getCurrency(); final String description = ""; final BillingPeriod billingPeriod = previousEvent.getBillingPeriod(); final SubscriptionBaseTransitionType type = SubscriptionBaseTransitionType.END_BILLING_DISABLED; final Long totalOrdering = globaltotalOrder.getAndIncrement(); return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice, currency, billingPeriod, billCycleDay, description, totalOrdering, type, catalog, false); }
private Integer computeAccountBCD(final BillingEventSet result) throws CatalogApiException { BillingEvent oldestAccountAlignedBillingEvent = null; for (final BillingEvent event : result) { if (event.getBillingAlignment() != BillingAlignment.ACCOUNT) { continue; } final BigDecimal recurringPrice = event.getRecurringPrice(event.getEffectiveDate()); final boolean hasRecurringPrice = recurringPrice != null; // Note: could be zero (BCD would still be set, by convention) final boolean hasUsage = event.getUsages() != null && !event.getUsages().isEmpty(); if (!hasRecurringPrice && !hasUsage) { // Nothing to bill, ignored for the purpose of BCD calculation continue; } if (oldestAccountAlignedBillingEvent == null || event.getEffectiveDate().compareTo(oldestAccountAlignedBillingEvent.getEffectiveDate()) < 0 || (event.getEffectiveDate().compareTo(oldestAccountAlignedBillingEvent.getEffectiveDate()) == 0 && event.getTotalOrdering().compareTo(oldestAccountAlignedBillingEvent.getTotalOrdering()) < 0)) { oldestAccountAlignedBillingEvent = event; } } if (oldestAccountAlignedBillingEvent == null) { return null; } // BCD in the account timezone final int accountBCDCandidate = oldestAccountAlignedBillingEvent.getBillCycleDayLocal(); Preconditions.checkState(accountBCDCandidate > 0, "Wrong Account BCD calculation for event: " + oldestAccountAlignedBillingEvent); return accountBCDCandidate; }
protected BillingEvent createNewDisableEvent(final DateTime disabledDurationStart, final BillingEvent previousEvent, final Catalog catalog) throws CatalogApiException { final int billCycleDay = previousEvent.getBillCycleDayLocal(); final SubscriptionBase subscription = previousEvent.getSubscription(); final DateTime effectiveDate = disabledDurationStart; final PlanPhase planPhase = previousEvent.getPlanPhase(); final Plan plan = previousEvent.getPlan(); // Make sure to set the fixed price to null and the billing period to NO_BILLING_PERIOD, // which makes invoice disregard this event final BigDecimal fixedPrice = null; final BillingPeriod billingPeriod = BillingPeriod.NO_BILLING_PERIOD; final Currency currency = previousEvent.getCurrency(); final String description = ""; final SubscriptionBaseTransitionType type = SubscriptionBaseTransitionType.START_BILLING_DISABLED; final Long totalOrdering = globaltotalOrder.getAndIncrement(); return new DefaultBillingEvent(subscription, effectiveDate, true, plan, planPhase, fixedPrice, currency, billingPeriod, billCycleDay, description, totalOrdering, type, catalog, true); }
protected BillingEvent createMockBillingEvent(final int bcd, final DateTime effectiveDate, final BillingPeriod billingPeriod, final List<Usage> usages) { final BillingEvent result = Mockito.mock(BillingEvent.class); Mockito.when(result.getCurrency()).thenReturn(Currency.BTC); Mockito.when(result.getBillCycleDayLocal()).thenReturn(bcd); Mockito.when(result.getEffectiveDate()).thenReturn(effectiveDate); Mockito.when(result.getBillingPeriod()).thenReturn(billingPeriod); final Account account = Mockito.mock(Account.class); Mockito.when(account.getId()).thenReturn(accountId); final SubscriptionBase subscription = Mockito.mock(SubscriptionBase.class); Mockito.when(subscription.getId()).thenReturn(subscriptionId); Mockito.when(subscription.getBundleId()).thenReturn(bundleId); Mockito.when(result.getSubscription()).thenReturn(subscription); final Product product = Mockito.mock(Product.class); Mockito.when(product.getName()).thenReturn(productName); final Plan plan = Mockito.mock(Plan.class); Mockito.when(plan.getName()).thenReturn(planName); Mockito.when(plan.getProduct()).thenReturn(product); Mockito.when(result.getPlan()).thenReturn(plan); final PlanPhase phase = Mockito.mock(PlanPhase.class); Mockito.when(phase.getName()).thenReturn(phaseName); Mockito.when(result.getPlanPhase()).thenReturn(phase); Mockito.when(result.getUsages()).thenReturn(usages); return result; }
final LocalDate maxEndDate = thisEvent.getPlanPhase().getPhaseType() == PhaseType.FIXEDTERM ? thisEvent.getPlanPhase().getDuration().addToLocalDate(internalCallContext.toLocalDate(thisEvent.getEffectiveDate())) : null; final BillingPeriod billingPeriod = thisEvent.getBillingPeriod(); if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) { final LocalDate startDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate()); final LocalDate endDate = (nextEvent == null) ? null : internalCallContext.toLocalDate(nextEvent.getEffectiveDate()); final int billCycleDayLocal = thisEvent.getBillCycleDayLocal(); 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().getProduct().getName(), thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(), itemDatum.getStartDate(), itemDatum.getEndDate(), updatePerSubscriptionNextNotificationDate(thisEvent.getSubscription().getId(), itemDataWithNextBillingCycleDate.getNextBillingCycleDate(), items, billingMode, perSubscriptionFutureNotificationDate);
subscription.getId(), null, event.getPlan().getName(), event.getPlanPhase().getName(), startDate, startDate.plusDays(29),
private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final UUID accountId, final BillingEvent thisEvent, final LocalDate targetDate, final Currency currency, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger, final InternalCallContext internalCallContext) throws InvoiceApiException { final LocalDate roundedStartDate = internalCallContext.toLocalDate(thisEvent.getEffectiveDate()); if (roundedStartDate.isAfter(targetDate)) { return null; } else { final BigDecimal fixedPrice = thisEvent.getFixedPrice(); if (fixedPrice != null) { final FixedPriceInvoiceItem fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, accountId, thisEvent.getSubscription().getBundleId(), thisEvent.getSubscription().getId(), thisEvent.getPlan().getProduct().getName(), thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(), roundedStartDate, fixedPrice, currency); // For debugging purposes invoiceItemGeneratorLogger.append(thisEvent, fixedPriceInvoiceItem); return fixedPriceInvoiceItem; } else { return null; } } }
@Override public int compareTo(final BillingEvent e1) { if (!getSubscription().getId().equals(e1.getSubscription().getId())) { // First order by subscription return getSubscription().getId().compareTo(e1.getSubscription().getId()); } else { // subscriptions are the same if (!getEffectiveDate().equals(e1.getEffectiveDate())) { // Secondly order by date return getEffectiveDate().compareTo(e1.getEffectiveDate()); } else { // dates and subscriptions are the same if (SubscriptionBaseTransitionType.END_BILLING_DISABLED.equals(e1.getTransitionType())) { return 1; } else if (SubscriptionBaseTransitionType.START_BILLING_DISABLED.equals(e1.getTransitionType())) { if (SubscriptionBaseTransitionType.END_BILLING_DISABLED.equals(getTransitionType())) { if (SubscriptionBaseTransitionType.START_BILLING_DISABLED.equals(e1.getTransitionType())) { return -1; } else if (SubscriptionBaseTransitionType.END_BILLING_DISABLED.equals(e1.getTransitionType())) { if (SubscriptionBaseTransitionType.START_BILLING_DISABLED.equals(getTransitionType())) { return getTotalOrdering().compareTo(e1.getTotalOrdering());
final BillingEvent event = events.next(); final LocalDate eventLocalEffectiveDate = internalCallContext.toLocalDate(event.getEffectiveDate()); if (eventLocalEffectiveDate.isAfter(targetDate)) { continue; Iterables.any(event.getUsages(), new Predicate<Usage>() { @Override public boolean apply(@Nullable final Usage input) { final UUID subscriptionId = event.getSubscription().getId(); if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) { final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsgRes.getRawUsage(), rawUsgRes.getExistingTrackingIds(), targetDate, rawUsgRes.getRawUsageStartDate(), usageDetailMode, internalCallContext);
@Override public int compareTo(final BillingEvent e1) { if (!getSubscription().getId().equals(e1.getSubscription().getId())) { // First order by subscription return getSubscription().getId().compareTo(e1.getSubscription().getId()); } else { // subscriptions are the same if (!getEffectiveDate().equals(e1.getEffectiveDate())) { // Secondly order by date return getEffectiveDate().compareTo(e1.getEffectiveDate()); } else { // dates and subscriptions are the same return getTotalOrdering().compareTo(e1.getTotalOrdering()); } } } };
@Override public UUID apply(final BillingEvent input) { return input.getSubscription().getId(); } });
private Map<UUID, DateTime> getNextTransitionsForSubscriptions(final BillingEventSet billingEvents) { final DateTime now = clock.getUTCNow(); final Map<UUID, DateTime> result = new HashMap<UUID, DateTime>(); for (final BillingEvent evt : billingEvents) { final UUID subscriptionId = evt.getSubscription().getId(); final DateTime evtEffectiveDate = evt.getEffectiveDate(); if (evtEffectiveDate.compareTo(now) <= 0) { continue; } final DateTime nextUpcomingPerSubscriptionDate = result.get(subscriptionId); if (nextUpcomingPerSubscriptionDate == null || nextUpcomingPerSubscriptionDate.compareTo(evtEffectiveDate) > 0) { result.put(subscriptionId, evtEffectiveDate); } } return result; }
private LocalDate computeNextNotificationDate() { LocalDate result = null; final Iterator<BillingEvent> eventIt = billingEvents.iterator(); BillingEvent nextEvent = eventIt.next(); while (eventIt.hasNext()) { final BillingEvent thisEvent = nextEvent; nextEvent = eventIt.next(); final LocalDate startDate = internalTenantContext.toLocalDate(thisEvent.getEffectiveDate()); final LocalDate endDate = internalTenantContext.toLocalDate(nextEvent.getEffectiveDate()); final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, endDate, targetDate, thisEvent.getBillCycleDayLocal(), usage.getBillingPeriod(), BillingMode.IN_ARREAR); final LocalDate nextBillingCycleDate = bid.getNextBillingCycleDate(); result = (result == null || result.compareTo(nextBillingCycleDate) < 0) ? nextBillingCycleDate : result; } final LocalDate startDate = internalTenantContext.toLocalDate(nextEvent.getEffectiveDate()); final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, null, targetDate, nextEvent.getBillCycleDayLocal(), usage.getBillingPeriod(), BillingMode.IN_ARREAR); final LocalDate nextBillingCycleDate = bid.getNextBillingCycleDate(); result = (result == null || result.compareTo(nextBillingCycleDate) < 0) ? nextBillingCycleDate : result; return result; }
private void processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events, final LocalDate targetDate, final Currency currency, final List<InvoiceItem> proposedItems, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate, final InternalCallContext internalCallContext) throws InvoiceApiException { if (events.isEmpty()) { return; } // Pretty-print the generated invoice items from the junction events final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger = new InvoiceItemGeneratorLogger(invoiceId, accountId, "recurring", log); final Iterator<BillingEvent> eventIt = events.iterator(); BillingEvent nextEvent = eventIt.next(); while (eventIt.hasNext()) { final BillingEvent thisEvent = nextEvent; nextEvent = eventIt.next(); if (!events.getSubscriptionIdsWithAutoInvoiceOff(). contains(thisEvent.getSubscription().getId())) { // don't consider events for subscriptions that have auto_invoice_off final BillingEvent adjustedNextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null; final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, thisEvent, adjustedNextEvent, targetDate, currency, invoiceItemGeneratorLogger, thisEvent.getPlan().getRecurringBillingMode(), perSubscriptionFutureNotificationDate, internalCallContext); proposedItems.addAll(newProposedItems); } } final List<InvoiceItem> newProposedItems = processRecurringEvent(invoiceId, accountId, nextEvent, null, targetDate, currency, invoiceItemGeneratorLogger, nextEvent.getPlan().getRecurringBillingMode(), perSubscriptionFutureNotificationDate, internalCallContext); proposedItems.addAll(newProposedItems); invoiceItemGeneratorLogger.logItems(); }
protected BillingEvent precedingBillingEventForSubscription(final DateTime disabledDurationStart, final SortedSet<BillingEvent> subscriptionBillingEvents) { if (disabledDurationStart == null) { return null; } // We look for the first billingEvent strictly prior our disabledDurationStart or null if none BillingEvent prev = null; for (final BillingEvent event : subscriptionBillingEvents) { if (!event.getEffectiveDate().isBefore(disabledDurationStart)) { return prev; } else { prev = event; } } return prev; }
@Override public List<Usage> apply(final BillingEvent input) { return input.getUsages(); } }));
public String getPlanName() { return billingEvents.get(0).getPlan().getName(); }
public Currency getCurrency() { return billingEvents.get(0).getCurrency(); }
public String getPhaseName() { return billingEvents.get(0).getPlanPhase().getName(); }
subscription.getId(), null, event.getPlan().getName(), event.getPlanPhase().getName(), startDate, startDate.plusDays(29),