use of org.killbill.billing.invoice.model.InvalidDateSequenceException in project killbill by killbill.
the class FixedAndRecurringInvoiceItemGenerator method generateInvoiceItemData.
public RecurringInvoiceItemDataWithNextBillingCycleDate generateInvoiceItemData(final LocalDate startDate, @Nullable final LocalDate endDate, final LocalDate targetDate, final int billingCycleDayLocal, final BillingPeriod billingPeriod, final BillingMode billingMode) throws InvalidDateSequenceException {
if (endDate != null && endDate.isBefore(startDate)) {
throw new InvalidDateSequenceException();
}
if (targetDate.isBefore(startDate)) {
throw new InvalidDateSequenceException();
}
final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(startDate, endDate, targetDate, billingCycleDayLocal, billingPeriod, billingMode);
// We are not billing for less than a day
if (!billingIntervalDetail.hasSomethingToBill()) {
return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail);
}
//
if (endDate != null && !endDate.isAfter(billingIntervalDetail.getFirstBillingCycleDate())) {
final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod);
final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods);
results.add(itemData);
return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail);
}
//
if (billingIntervalDetail.getFirstBillingCycleDate().isAfter(startDate)) {
final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, billingIntervalDetail.getFirstBillingCycleDate(), billingPeriod);
if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
// Not common - add info in the logs for debugging purposes
final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, billingIntervalDetail.getFirstBillingCycleDate(), leadingProRationPeriods);
log.info("Adding pro-ration: {}", itemData);
results.add(itemData);
}
}
//
// Calculate the effectiveEndDate from the firstBillingCycleDate:
// - If endDate != null and targetDate is after endDate => this is the endDate and will lead to a trailing pro-ration
// - If not, this is the last billingCycleDate calculation right after the targetDate
//
final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate();
//
// Based on what we calculated previously, code recompute one more time the numberOfWholeBillingPeriods
//
final LocalDate lastBillingCycleDate = billingIntervalDetail.getLastBillingCycleDate();
final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(billingIntervalDetail.getFirstBillingCycleDate(), lastBillingCycleDate, billingPeriod);
for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
final LocalDate servicePeriodStartDate;
if (!results.isEmpty()) {
// Make sure the periods align, especially with the pro-ration calculations above
servicePeriodStartDate = results.get(results.size() - 1).getEndDate();
} else if (i == 0) {
// Use the specified start date
servicePeriodStartDate = startDate;
} else {
throw new IllegalStateException("We should at least have one invoice item!");
}
// Make sure to align the end date with the BCD
final LocalDate servicePeriodEndDate = billingIntervalDetail.getFutureBillingDateFor(i + 1);
results.add(new RecurringInvoiceItemData(servicePeriodStartDate, servicePeriodEndDate, BigDecimal.ONE));
}
//
if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
final BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
// Not common - add info in the logs for debugging purposes
final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods);
log.info("Adding trailing pro-ration: {}", itemData);
results.add(itemData);
}
}
return new RecurringInvoiceItemDataWithNextBillingCycleDate(results, billingIntervalDetail);
}
use of org.killbill.billing.invoice.model.InvalidDateSequenceException in project killbill by killbill.
the class ProRationTestBase method testCalculateNumberOfBillingCycles.
protected void testCalculateNumberOfBillingCycles(final LocalDate startDate, final LocalDate endDate, final LocalDate targetDate, final int billingCycleDay, final BigDecimal expectedValue) throws InvalidDateSequenceException {
try {
final BigDecimal numberOfBillingCycles;
numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay);
assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0, "Actual: " + numberOfBillingCycles.toString() + "; expected: " + expectedValue.toString());
} catch (InvalidDateSequenceException idse) {
throw idse;
} catch (Exception e) {
fail("Unexpected exception: " + e.getMessage());
}
}
use of org.killbill.billing.invoice.model.InvalidDateSequenceException 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);
}
}
Aggregations