SortedSet<Invoice> unpaidInvoicesForAccount(final UUID accountId, final InternalCallContext context) { final Collection<Invoice> invoices = invoiceApi.getUnpaidInvoicesByAccountId(accountId, context.toLocalDate(context.getCreatedDate()), context); final SortedSet<Invoice> sortedInvoices = new TreeSet<Invoice>(new InvoiceDateComparator()); sortedInvoices.addAll(invoices); return sortedInvoices; } }
private void cancelSubscriptionsIfRequired(final DateTime effectiveDate, final ImmutableAccountData account, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException { if (nextOverdueState.getOverdueCancellationPolicy() == OverdueCancellationPolicy.NONE) { return; } final CallContext callContext = internalCallContextFactory.createCallContext(context); try { final BillingActionPolicy actionPolicy; switch (nextOverdueState.getOverdueCancellationPolicy()) { case END_OF_TERM: actionPolicy = BillingActionPolicy.END_OF_TERM; break; case IMMEDIATE: actionPolicy = BillingActionPolicy.IMMEDIATE; break; default: throw new IllegalStateException("Unexpected OverdueCancellationPolicy " + nextOverdueState.getOverdueCancellationPolicy()); } final List<Entitlement> toBeCancelled = new LinkedList<Entitlement>(); computeEntitlementsToCancel(account, toBeCancelled, callContext); try { entitlementInternalApi.cancel(toBeCancelled, context.toLocalDate(effectiveDate), actionPolicy, ImmutableList.<PluginProperty>of(), context); } catch (final EntitlementApiException e) { throw new OverdueException(e); } } catch (final EntitlementApiException e) { throw new OverdueException(e); } }
@VisibleForTesting DateTime getEffectiveDateForNewBCD(final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) { if (internalCallContext.getAccountRecordId() == null) { throw new IllegalStateException("Need to have a valid context with accountRecordId"); } // Today as seen by this account final LocalDate startDate = effectiveFromDate != null ? effectiveFromDate : internalCallContext.toLocalDate(internalCallContext.getCreatedDate()); // We want to compute a LocalDate in account TZ which maps to the provided 'bcd' and then compute an effectiveDate for when that BCD_CHANGE event needs to be triggered // // There is a bit of complexity to make sure the date we chose exists (e.g: a BCD of 31 in a february month would not make sense). final int currentDay = startDate.getDayOfMonth(); final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue(); final LocalDate requestedDate; if (bcd < currentDay) { final LocalDate startDatePlusOneMonth = startDate.plusMonths(1); final int lastDayOfNextMonth = startDatePlusOneMonth.dayOfMonth().getMaximumValue(); final int originalBCDORLastDayOfMonth = bcd <= lastDayOfNextMonth ? bcd : lastDayOfNextMonth; requestedDate = new LocalDate(startDatePlusOneMonth.getYear(), startDatePlusOneMonth.getMonthOfYear(), originalBCDORLastDayOfMonth); } else if (bcd == currentDay && effectiveFromDate == null) { // will default to immediate event requestedDate = null; } else if (bcd <= lastDayOfMonth) { requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), bcd); } else /* bcd > lastDayOfMonth && bcd > currentDay */ { requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), lastDayOfMonth); } return requestedDate == null ? internalCallContext.getCreatedDate() : internalCallContext.toUTCDateTime(requestedDate); }
private OverdueState refreshWithLock(final DateTime effectiveDate, final InternalCallContext context) throws OverdueException, OverdueApiException { final BillingState billingState = billingState(context); final BlockingState blockingStateForService = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context); final String previousOverdueStateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME; final OverdueState currentOverdueState = overdueStateSet.findState(previousOverdueStateName); final OverdueState nextOverdueState = overdueStateSet.calculateOverdueState(billingState, context.toLocalDate(context.getCreatedDate())); overdueStateApplicator.apply(effectiveDate, overdueStateSet, billingState, overdueable, currentOverdueState, nextOverdueState, context); return nextOverdueState; }
@Override public LocalDate apply(final DateTime input) { return internalCallContext.toLocalDate(input); } }));
private void populateNextFutureNotificationDate(final DateTime notificationDateTime, final Set<UUID> subscriptionIds, final FutureAccountNotificationsBuilder notificationsBuilder, final InternalCallContext context) { final LocalDate notificationDate = context.toLocalDate(notificationDateTime); notificationsBuilder.setNotificationListForTrigger(ImmutableMap.<LocalDate, Set<UUID>>of(notificationDate, subscriptionIds)); }
public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final boolean isRescheduled, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) { final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken); try { dispatcher.processSubscriptionForInvoiceGeneration(subscriptionId, context.toLocalDate(eventDateTime), isRescheduled, context); } catch (final InvoiceApiException e) { log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e); } }
public void handleEventForInvoiceNotification(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) { final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken); try { dispatcher.processSubscriptionForInvoiceNotification(subscriptionId, context.toLocalDate(eventDateTime), context); } catch (final InvoiceApiException e) { log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e); } }
private LocalDate trackInvoiceItemCreatedDay(final InvoiceItem invoiceItem, final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription, final InternalCallContext internalCallContext) { final UUID subscriptionId = invoiceItem.getSubscriptionId(); if (subscriptionId == null) { return null; } final LocalDate createdDay = internalCallContext.toLocalDate(MoreObjects.firstNonNull(invoiceItem.getCreatedDate(), internalCallContext.getCreatedDate())); createdItemsPerDayPerSubscription.put(subscriptionId, createdDay); return createdDay; } }
SortedSet<Invoice> unpaidInvoicesForAccount(final UUID accountId, final InternalCallContext context) { final Collection<Invoice> invoices = invoiceApi.getUnpaidInvoicesByAccountId(accountId, context.toLocalDate(context.getCreatedDate()), context); final SortedSet<Invoice> sortedInvoices = new TreeSet<Invoice>(new InvoiceDateComparator()); sortedInvoices.addAll(invoices); return sortedInvoices; } }
private void populateNextFutureDryRunNotificationDate(final BillingEventSet billingEvents, final FutureAccountNotificationsBuilder notificationsBuilder, final InternalCallContext context) { final Map<LocalDate, Set<UUID>> notificationListForTrigger = notificationsBuilder.getNotificationListForTrigger(); final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(context).getMillis(); final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0; final Map<LocalDate, Set<UUID>> notificationListForDryRun = isInvoiceNotificationEnabled ? new HashMap<LocalDate, Set<UUID>>() : ImmutableMap.<LocalDate, Set<UUID>>of(); if (isInvoiceNotificationEnabled) { for (final LocalDate curDate : notificationListForTrigger.keySet()) { final LocalDate curDryRunDate = context.toLocalDate(context.toUTCDateTime(curDate).minus(dryRunNotificationTime)); Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate); if (subscriptionsForDryRunDates == null) { subscriptionsForDryRunDates = new HashSet<UUID>(); notificationListForDryRun.put(curDryRunDate, subscriptionsForDryRunDates); } subscriptionsForDryRunDates.addAll(notificationListForTrigger.get(curDate)); } final Map<UUID, DateTime> upcomingTransitionsForSubscriptions = isInvoiceNotificationEnabled ? getNextTransitionsForSubscriptions(billingEvents) : ImmutableMap.<UUID, DateTime>of(); for (UUID curId : upcomingTransitionsForSubscriptions.keySet()) { final LocalDate curDryRunDate = context.toLocalDate(upcomingTransitionsForSubscriptions.get(curId).minus(dryRunNotificationTime)); Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate); if (subscriptionsForDryRunDates == null) { subscriptionsForDryRunDates = new HashSet<UUID>(); notificationListForDryRun.put(curDryRunDate, subscriptionsForDryRunDates); } subscriptionsForDryRunDates.add(curId); } } notificationsBuilder.setNotificationListForDryRun(notificationListForDryRun); }
private LocalDate getMinBillingEventDate(final BillingEventSet eventSet, final InternalCallContext internalCallContext) { DateTime minDate = null; for (final BillingEvent cur : eventSet) { if (minDate == null || minDate.compareTo(cur.getEffectiveDate()) > 0) { minDate = cur.getEffectiveDate(); } } return internalCallContext.toLocalDate(minDate); }
public void processSubscriptionForInvoiceGeneration(final EffectiveSubscriptionInternalEvent transition, final InternalCallContext context) throws InvoiceApiException { final UUID subscriptionId = transition.getSubscriptionId(); final LocalDate targetDate = context.toLocalDate(transition.getEffectiveTransitionTime()); processSubscriptionForInvoiceGeneration(subscriptionId, targetDate, false, context); }
@Override public UUID createMigrationInvoice(final UUID accountId, final LocalDate targetDate, final Iterable<InvoiceItem> items, final CallContext context) { final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context); final LocalDate invoiceDate = internalCallContext.toLocalDate(internalCallContext.getCreatedDate()); final InvoiceModelDao migrationInvoice = new InvoiceModelDao(accountId, invoiceDate, targetDate, items.iterator().next().getCurrency(), true);
@VisibleForTesting boolean isSameDayAndSameSubscription(final InvoiceItem prevComputedFixedItem, final BillingEvent currentBillingEvent, final InternalCallContext internalCallContext) { final LocalDate curLocalEffectiveDate = internalCallContext.toLocalDate(currentBillingEvent.getEffectiveDate()); if (prevComputedFixedItem != null && /* If we have computed a previous item */ prevComputedFixedItem.getStartDate().compareTo(curLocalEffectiveDate) == 0 && /* The current billing event happens at the same date */ prevComputedFixedItem.getSubscriptionId().compareTo(currentBillingEvent.getSubscription().getId()) == 0 /* The current billing event happens for the same subscription */) { return true; } else { return false; } }
private void cancelSubscriptionsIfRequired(final DateTime effectiveDate, final ImmutableAccountData account, final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException { if (nextOverdueState.getOverdueCancellationPolicy() == OverdueCancellationPolicy.NONE) { return; } final CallContext callContext = internalCallContextFactory.createCallContext(context); try { final BillingActionPolicy actionPolicy; switch (nextOverdueState.getOverdueCancellationPolicy()) { case END_OF_TERM: actionPolicy = BillingActionPolicy.END_OF_TERM; break; case IMMEDIATE: actionPolicy = BillingActionPolicy.IMMEDIATE; break; default: throw new IllegalStateException("Unexpected OverdueCancellationPolicy " + nextOverdueState.getOverdueCancellationPolicy()); } final List<Entitlement> toBeCancelled = new LinkedList<Entitlement>(); computeEntitlementsToCancel(account, toBeCancelled, callContext); try { entitlementInternalApi.cancel(toBeCancelled, context.toLocalDate(effectiveDate), actionPolicy, ImmutableList.<PluginProperty>of(), context); } catch (final EntitlementApiException e) { throw new OverdueException(e); } } catch (final EntitlementApiException e) { throw new OverdueException(e); } }
@VisibleForTesting DateTime getEffectiveDateForNewBCD(final int bcd, @Nullable final LocalDate effectiveFromDate, final InternalCallContext internalCallContext) { if (internalCallContext.getAccountRecordId() == null) { throw new IllegalStateException("Need to have a valid context with accountRecordId"); } // Today as seen by this account final LocalDate startDate = effectiveFromDate != null ? effectiveFromDate : internalCallContext.toLocalDate(internalCallContext.getCreatedDate()); // We want to compute a LocalDate in account TZ which maps to the provided 'bcd' and then compute an effectiveDate for when that BCD_CHANGE event needs to be triggered // // There is a bit of complexity to make sure the date we chose exists (e.g: a BCD of 31 in a february month would not make sense). final int currentDay = startDate.getDayOfMonth(); final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue(); final LocalDate requestedDate; if (bcd < currentDay) { final LocalDate startDatePlusOneMonth = startDate.plusMonths(1); final int lastDayOfNextMonth = startDatePlusOneMonth.dayOfMonth().getMaximumValue(); final int originalBCDORLastDayOfMonth = bcd <= lastDayOfNextMonth ? bcd : lastDayOfNextMonth; requestedDate = new LocalDate(startDatePlusOneMonth.getYear(), startDatePlusOneMonth.getMonthOfYear(), originalBCDORLastDayOfMonth); } else if (bcd == currentDay && effectiveFromDate == null) { // will default to immediate event requestedDate = null; } else if (bcd <= lastDayOfMonth) { requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), bcd); } else /* bcd > lastDayOfMonth && bcd > currentDay */ { requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), lastDayOfMonth); } return requestedDate == null ? internalCallContext.getCreatedDate() : internalCallContext.toUTCDateTime(requestedDate); }
final LocalDate adjustedTargetDate = adjustTargetDate(existingInvoices, targetDate); final LocalDate invoiceDate = context.toLocalDate(context.getCreatedDate()); final InvoiceStatus invoiceStatus = events.isAccountAutoInvoiceDraft() ? InvoiceStatus.DRAFT : InvoiceStatus.COMMITTED; final DefaultInvoice invoice = targetInvoiceId != null ?
private OverdueState refreshWithLock(final DateTime effectiveDate, final InternalCallContext context) throws OverdueException, OverdueApiException { final BillingState billingState = billingState(context); final BlockingState blockingStateForService = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context); final String previousOverdueStateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME; final OverdueState currentOverdueState = overdueStateSet.findState(previousOverdueStateName); final OverdueState nextOverdueState = overdueStateSet.calculateOverdueState(billingState, context.toLocalDate(context.getCreatedDate())); overdueStateApplicator.apply(effectiveDate, overdueStateSet, billingState, overdueable, currentOverdueState, nextOverdueState, context); return nextOverdueState; }
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; } } }