use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.
the class DefaultInvoiceUserApi method triggerInvoiceGeneration.
@Override
public Invoice triggerInvoiceGeneration(final UUID accountId, @Nullable final LocalDate targetDate, final DryRunArguments dryRunArguments, final CallContext context) throws InvoiceApiException {
final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(accountId, context);
final Invoice result = dispatcher.processAccount(true, accountId, targetDate, dryRunArguments, internalContext);
if (result == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOTHING_TO_DO, accountId, targetDate != null ? targetDate : "null");
} else {
return result;
}
}
use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.
the class DefaultInvoiceDao method deleteCBA.
@Override
public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final InternalCallContext context) throws InvoiceApiException {
final List<Tag> invoicesTags = getInvoicesTags(context);
transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
// Retrieve the invoice and make sure it belongs to the right account
final InvoiceModelDao invoice = transactional.getById(invoiceId.toString(), context);
if (invoice == null || !invoice.getAccountId().equals(accountId)) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
}
// Retrieve the invoice item and make sure it belongs to the right invoice
final InvoiceItemSqlDao invoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
final InvoiceItemModelDao cbaItem = invoiceItemSqlDao.getById(invoiceItemId.toString(), context);
if (cbaItem == null || !cbaItem.getInvoiceId().equals(invoice.getId())) {
throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
}
// First, adjust the same invoice with the CBA amount to "delete"
final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoice.getId(), invoice.getAccountId(), null, null, null, null, null, null, context.getCreatedDate().toLocalDate(), null, cbaItem.getAmount().negate(), null, cbaItem.getCurrency(), cbaItem.getId());
createInvoiceItemFromTransaction(invoiceItemSqlDao, cbaAdjItem, context);
// Verify the final invoice balance is not negative
invoiceDaoHelper.populateChildren(invoice, invoicesTags, entitySqlDaoWrapperFactory, context);
if (InvoiceModelDaoHelper.getBalance(invoice).compareTo(BigDecimal.ZERO) < 0) {
throw new InvoiceApiException(ErrorCode.INVOICE_WOULD_BE_NEGATIVE);
}
// If there is more account credit than CBA we adjusted, we're done.
// Otherwise, we need to find further invoices on which this credit was consumed
final BigDecimal accountCBA = cbaDao.getAccountCBAFromTransaction(entitySqlDaoWrapperFactory, context);
if (accountCBA.compareTo(BigDecimal.ZERO) < 0) {
if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
}
final List<InvoiceModelDao> invoicesFollowing = getAllNonMigratedInvoicesByAccountAfterDate(transactional, invoice.getInvoiceDate(), context);
invoiceDaoHelper.populateChildren(invoicesFollowing, invoicesTags, entitySqlDaoWrapperFactory, context);
// The remaining amount to adjust (i.e. the amount of credits used on following invoices)
// is the current account CBA balance (minus the sign)
BigDecimal positiveRemainderToAdjust = accountCBA.negate();
for (final InvoiceModelDao invoiceFollowing : invoicesFollowing) {
if (invoiceFollowing.getId().equals(invoice.getId())) {
continue;
}
// Add a single adjustment per invoice
BigDecimal positiveCBAAdjItemAmount = BigDecimal.ZERO;
for (final InvoiceItemModelDao cbaUsed : invoiceFollowing.getInvoiceItems()) {
// Ignore non CBA items or credits (CBA >= 0)
if (!InvoiceItemType.CBA_ADJ.equals(cbaUsed.getType()) || cbaUsed.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
continue;
}
final BigDecimal positiveCBAUsedAmount = cbaUsed.getAmount().negate();
final BigDecimal positiveNextCBAAdjItemAmount;
if (positiveCBAUsedAmount.compareTo(positiveRemainderToAdjust) < 0) {
positiveNextCBAAdjItemAmount = positiveCBAUsedAmount;
positiveRemainderToAdjust = positiveRemainderToAdjust.subtract(positiveNextCBAAdjItemAmount);
} else {
positiveNextCBAAdjItemAmount = positiveRemainderToAdjust;
positiveRemainderToAdjust = BigDecimal.ZERO;
}
positiveCBAAdjItemAmount = positiveCBAAdjItemAmount.add(positiveNextCBAAdjItemAmount);
if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
break;
}
}
// Add the adjustment on that invoice
final InvoiceItemModelDao nextCBAAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoiceFollowing.getId(), invoice.getAccountId(), null, null, null, null, null, null, context.getCreatedDate().toLocalDate(), null, positiveCBAAdjItemAmount, null, cbaItem.getCurrency(), cbaItem.getId());
createInvoiceItemFromTransaction(invoiceItemSqlDao, nextCBAAdjItem, context);
if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
break;
}
}
}
return null;
}
});
}
use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.
the class InvoiceDaoHelper method computeItemAdjustmentsForTargetInvoiceItem.
private static void computeItemAdjustmentsForTargetInvoiceItem(final InvoiceItemModelDao targetInvoiceItem, final List<InvoiceItemModelDao> adjustedOrRepairedItems, final Map<UUID, BigDecimal> inputAdjInvoiceItem, final Map<UUID, BigDecimal> outputAdjInvoiceItem) throws InvoiceApiException {
final BigDecimal originalItemAmount = targetInvoiceItem.getAmount();
final BigDecimal maxAdjLeftAmount = computeItemAdjustmentAmount(originalItemAmount, adjustedOrRepairedItems);
final BigDecimal proposedItemAmount = inputAdjInvoiceItem.get(targetInvoiceItem.getId());
if (proposedItemAmount != null && proposedItemAmount.compareTo(maxAdjLeftAmount) > 0) {
throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, proposedItemAmount, maxAdjLeftAmount);
}
final BigDecimal itemAmountToAdjust = MoreObjects.firstNonNull(proposedItemAmount, maxAdjLeftAmount);
if (itemAmountToAdjust.compareTo(BigDecimal.ZERO) > 0) {
outputAdjInvoiceItem.put(targetInvoiceItem.getId(), itemAmountToAdjust);
}
}
use of org.killbill.billing.invoice.api.InvoiceApiException in project killbill by killbill.
the class InvoiceListener method handleEventForInvoiceNotification.
public void handleEventForInvoiceNotification(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
try {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
dispatcher.processSubscriptionForInvoiceNotification(subscriptionId, context.toLocalDate(eventDateTime), context);
} catch (InvoiceApiException e) {
log.warn("Unable to process subscriptionId='{}', eventDateTime='{}'", subscriptionId, eventDateTime, e);
}
}
use of org.killbill.billing.invoice.api.InvoiceApiException 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