use of org.killbill.billing.invoice.dao.InvoiceModelDao in project killbill by killbill.
the class InvoiceDispatcher method processAccountWithLockAndInputTargetDate.
private Invoice processAccountWithLockAndInputTargetDate(final UUID accountId, final LocalDate targetDate, final BillingEventSet billingEvents, final boolean isDryRun, final InternalCallContext context) throws InvoiceApiException {
try {
final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
final List<Invoice> invoices = billingEvents.isAccountAutoInvoiceOff() ? ImmutableList.<Invoice>of() : ImmutableList.<Invoice>copyOf(Collections2.transform(invoiceDao.getInvoicesByAccount(context), new Function<InvoiceModelDao, Invoice>() {
@Override
public Invoice apply(final InvoiceModelDao input) {
return new DefaultInvoice(input);
}
}));
final Currency targetCurrency = account.getCurrency();
final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, billingEvents, invoices, targetDate, targetCurrency, context);
final DefaultInvoice invoice = invoiceWithMetadata.getInvoice();
// Compute future notifications
final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, context);
//
if (invoice == null) {
if (isDryRun) {
log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
} else {
log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, targetDate);
final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(), context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, context);
postEvent(event);
}
return null;
}
// Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin
final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, context);
DefaultInvoice tmpInvoiceForInvoicePlugins = invoice;
if (cbaItemPreInvoicePlugins != null) {
tmpInvoiceForInvoicePlugins = (DefaultInvoice) tmpInvoiceForInvoicePlugins.clone();
tmpInvoiceForInvoicePlugins.addInvoiceItem(cbaItemPreInvoicePlugins);
}
//
// Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
//
final CallContext callContext = buildCallContext(context);
final List<InvoiceItem> additionalInvoiceItemsFromPlugins = invoicePluginDispatcher.getAdditionalInvoiceItems(tmpInvoiceForInvoicePlugins, isDryRun, callContext);
if (additionalInvoiceItemsFromPlugins.isEmpty()) {
// PERF: avoid re-computing the CBA if no change was made
if (cbaItemPreInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPreInvoicePlugins);
}
} else {
invoice.addInvoiceItems(additionalInvoiceItemsFromPlugins);
// Use credit after we call the plugin (https://github.com/killbill/killbill/issues/637)
final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, context);
if (cbaItemPostInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPostInvoicePlugins);
}
}
if (!isDryRun) {
// Compute whether this is a new invoice object (or just some adjustments on an existing invoice), and extract invoiceIds for later use
final Set<UUID> uniqueInvoiceIds = getUniqueInvoiceIds(invoice);
final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId());
final Set<UUID> adjustedUniqueOtherInvoiceId = uniqueInvoiceIds;
logInvoiceWithItems(account, invoice, targetDate, adjustedUniqueOtherInvoiceId, isRealInvoiceWithItems);
// Transformation to Invoice -> InvoiceModelDao
final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
final List<InvoiceItemModelDao> invoiceItemModelDaos = transformToInvoiceModelDao(invoice.getInvoiceItems());
invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
// Commit invoice on disk
final boolean isThereAnyItemsLeft = commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, futureAccountNotifications, context);
final boolean isRealInvoiceWithNonEmptyItems = isThereAnyItemsLeft ? isRealInvoiceWithItems : false;
setChargedThroughDates(invoice.getInvoiceItems(FixedPriceInvoiceItem.class), invoice.getInvoiceItems(RecurringInvoiceItem.class), context);
if (InvoiceStatus.COMMITTED.equals(invoice.getStatus())) {
notifyAccountIfEnabled(account, invoice, isRealInvoiceWithNonEmptyItems, context);
}
}
return invoice;
} catch (final AccountApiException e) {
log.error("Failed handling SubscriptionBase change.", e);
return null;
} catch (final SubscriptionBaseApiException e) {
log.error("Failed handling SubscriptionBase change.", e);
return null;
}
}
use of org.killbill.billing.invoice.dao.InvoiceModelDao in project killbill by killbill.
the class TestIntegrationInvoiceWithRepairLogic method testWithWrongInitialItem.
// This is a beatrix level test matching ur invoice TestSubscriptionItemTree#testWithWrongInitialItem
@Test(groups = "slow")
public void testWithWrongInitialItem() throws Exception {
final DateTime initialDate = new DateTime(2016, 9, 8, 0, 0, 0, 0, testTimeZone);
final LocalDate correctStartDate = initialDate.toLocalDate();
final LocalDate wrongStartDate = new LocalDate(2016, 9, 9);
final LocalDate endDate = new LocalDate(2016, 10, 8);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(8));
assertNotNull(account);
add_AUTO_INVOICING_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial");
busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CREATE);
final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec, null, null, null), null, correctStartDate, correctStartDate, false, false, ImmutableList.<PluginProperty>of(), callContext);
final Entitlement bpEntitlement = entitlementApi.getEntitlementForId(entitlementId, callContext);
assertListenerStatus();
final InvoiceModelDao existingBadInvoice = new InvoiceModelDao(UUID.randomUUID(), clock.getUTCNow(), account.getId(), null, correctStartDate, correctStartDate, Currency.USD, false, InvoiceStatus.COMMITTED, false);
final InvoiceItemModelDao wrongRecurring = new InvoiceItemModelDao(initialDate, InvoiceItemType.RECURRING, existingBadInvoice.getId(), existingBadInvoice.getAccountId(), bpEntitlement.getBundleId(), entitlementId, "", "Pistol", "pistol-monthly-notrial", "pistol-monthly-notrial-evergreen", null, null, new LocalDate(2016, 9, 9), new LocalDate(2016, 10, 8), new BigDecimal("19.29"), new BigDecimal("19.95"), account.getCurrency(), null);
existingBadInvoice.addInvoiceItem(wrongRecurring);
insertInvoiceItems(existingBadInvoice);
existingBadInvoice.getInvoiceItems().clear();
final InvoiceItemModelDao adj = new InvoiceItemModelDao(clock.getUTCNow(), InvoiceItemType.ITEM_ADJ, existingBadInvoice.getId(), existingBadInvoice.getAccountId(), null, null, null, wrongRecurring.getProductName(), wrongRecurring.getPlanName(), wrongRecurring.getPhaseName(), wrongRecurring.getUsageName(), wrongRecurring.getCatalogEffectiveDate(), clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("-19.29"), null, wrongRecurring.getCurrency(), wrongRecurring.getId());
existingBadInvoice.addInvoiceItem(adj);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT);
insertInvoiceItems(existingBadInvoice);
assertListenerStatus();
busHandler.pushExpectedEvents(NextEvent.INVOICE);
remove_AUTO_INVOICING_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2016, 9, 8), new LocalDate(2016, 9, 9), InvoiceItemType.RECURRING, new BigDecimal("0.66")));
checkNoMoreInvoiceToGenerate(account);
}
use of org.killbill.billing.invoice.dao.InvoiceModelDao in project killbill by killbill.
the class TestIntegrationInvoiceWithRepairLogic method testAdjustmentsToolarge.
@Test(groups = "slow")
public void testAdjustmentsToolarge() throws Exception {
final LocalDate today = new LocalDate(2013, 7, 19);
// Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
final String productName = "Shotgun";
final BillingPeriod term = BillingPeriod.ANNUAL;
//
// CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
//
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
final InvoiceItem targetItem = invoices.get(1).getInvoiceItems().get(0);
final InvoiceItemModelDao adj1 = new InvoiceItemModelDao(clock.getUTCNow(), InvoiceItemType.ITEM_ADJ, targetItem.getInvoiceId(), targetItem.getAccountId(), null, null, null, targetItem.getProductName(), targetItem.getPlanName(), targetItem.getPhaseName(), targetItem.getUsageName(), targetItem.getCatalogEffectiveDate(), clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("-1000.00"), null, targetItem.getCurrency(), targetItem.getId());
final InvoiceItemModelDao adj2 = new InvoiceItemModelDao(clock.getUTCNow(), InvoiceItemType.ITEM_ADJ, targetItem.getInvoiceId(), targetItem.getAccountId(), null, null, null, targetItem.getProductName(), targetItem.getPlanName(), targetItem.getPhaseName(), targetItem.getUsageName(), targetItem.getCatalogEffectiveDate(), clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("-2000.00"), null, targetItem.getCurrency(), targetItem.getId());
final InvoiceModelDao invoiceForAdj = new InvoiceModelDao(UUID.randomUUID(), clock.getUTCNow(), bpEntitlement.getAccountId(), null, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false, InvoiceStatus.COMMITTED, false);
invoiceForAdj.addInvoiceItem(adj1);
invoiceForAdj.addInvoiceItem(adj2);
busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
insertInvoiceItems(invoiceForAdj);
assertListenerStatus();
checkNoMoreInvoiceToGenerate(account);
}
use of org.killbill.billing.invoice.dao.InvoiceModelDao in project killbill by killbill.
the class InvoiceDispatcher method computeCBAOnExistingInvoice.
private InvoiceItem computeCBAOnExistingInvoice(final Invoice invoice, final InternalCallContext context) throws InvoiceApiException {
// Transformation to Invoice -> InvoiceModelDao
final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
final List<InvoiceItemModelDao> invoiceItemModelDaos = ImmutableList.copyOf(Collections2.transform(invoice.getInvoiceItems(), new Function<InvoiceItem, InvoiceItemModelDao>() {
@Override
public InvoiceItemModelDao apply(final InvoiceItem input) {
return new InvoiceItemModelDao(input);
}
}));
invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
final InvoiceItemModelDao cbaItem = invoiceDao.doCBAComplexity(invoiceModelDao, context);
return cbaItem != null ? InvoiceItemFactory.fromModelDao(cbaItem) : null;
}
use of org.killbill.billing.invoice.dao.InvoiceModelDao in project killbill by killbill.
the class InvoiceDispatcher method processAccountWithLockAndInputTargetDate.
private InvoiceWithFutureNotifications processAccountWithLockAndInputTargetDate(final UUID accountId, final LocalDate originalTargetDate, final BillingEventSet billingEvents, final AccountInvoices accountInvoices, @Nullable final DryRunInfo dryRunInfo, final boolean isRescheduled, final LinkedList<PluginProperty> pluginProperties, final Map<InvoiceTiming, Long> invoiceTimings, final InternalCallContext internalCallContext) throws InvoiceApiException {
final boolean isDryRun = dryRunInfo != null;
final CallContext callContext = buildCallContext(internalCallContext);
final ImmutableAccountData account;
try {
account = accountApi.getImmutableAccountDataById(accountId, internalCallContext);
} catch (final AccountApiException e) {
log.error("Unable to generate invoice for accountId='{}', a future notification has NOT been recorded", accountId, e);
long startNano = System.nanoTime();
invoicePluginDispatcher.onFailureCall(originalTargetDate, null, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
invoiceTimings.put(InvoiceTiming.PLUGINS_COMPLETION_CALL, System.nanoTime() - startNano);
return null;
}
long startNano = System.nanoTime();
final DateTime rescheduleDate = invoicePluginDispatcher.priorCall(originalTargetDate, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
invoiceTimings.put(InvoiceTiming.PLUGINS_PRIOR_CALL, System.nanoTime() - startNano);
if (rescheduleDate != null) {
if (isDryRun) {
log.warn("Ignoring rescheduleDate='{}', delayed scheduling is unsupported in dry-run", rescheduleDate);
} else {
final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(rescheduleDate, billingEvents, internalCallContext);
setFutureNotifications(account, futureAccountNotifications, internalCallContext);
}
return null;
}
startNano = System.nanoTime();
final InvoiceWithMetadata invoiceWithMetadata = generateKillBillInvoice(account, originalTargetDate, billingEvents, accountInvoices, dryRunInfo, internalCallContext);
invoiceTimings.put(InvoiceTiming.INVOICE_GENERATION, System.nanoTime() - startNano);
final DefaultInvoice invoice = invoiceWithMetadata.getInvoice();
// Compute future notifications
final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(invoiceWithMetadata, billingEvents, internalCallContext);
// If invoice comes back null, there is nothing new to generate, we can bail early
if (invoice == null) {
startNano = System.nanoTime();
invoicePluginDispatcher.onSuccessCall(originalTargetDate, null, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
invoiceTimings.put(InvoiceTiming.PLUGINS_COMPLETION_CALL, System.nanoTime() - startNano);
if (isDryRun) {
log.info("Generated null dryRun invoice for accountId='{}', targetDate='{}'", accountId, originalTargetDate);
} else {
log.info("Generated null invoice for accountId='{}', targetDate='{}'", accountId, originalTargetDate);
final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(), internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());
// Although we have a null invoice, it could be as a result of removing $0 USAGE (config#isUsageZeroAmountDisabled)
// and so we may still need to set the CTD for such subscriptions.
startNano = System.nanoTime();
setChargedThroughDatesNoExceptions(invoiceWithMetadata.getChargeThroughDates(), internalCallContext);
invoiceTimings.put(InvoiceTiming.SET_CHARGE_THROUGH_DT, System.nanoTime() - startNano);
setFutureNotifications(account, futureAccountNotifications, internalCallContext);
postEvent(event);
}
return null;
}
final LocalDate actualTargetDate = invoice.getTargetDate();
boolean success = false;
try {
// Generate missing credit (> 0 for generation and < 0 for use) prior we call the plugin(s)
final InvoiceItem cbaItemPreInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
if (cbaItemPreInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPreInvoicePlugins);
}
//
// Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
//
startNano = System.nanoTime();
final boolean invoiceUpdated = invoicePluginDispatcher.updateOriginalInvoiceWithPluginInvoiceItems(invoice, isDryRun, callContext, pluginProperties, internalCallContext);
invoiceTimings.put(InvoiceTiming.PLUGINS_ADDITIONAL_ITEMS, System.nanoTime() - startNano);
if (invoiceUpdated) {
// Remove the temporary CBA item as we need to re-compute CBA
if (cbaItemPreInvoicePlugins != null) {
invoice.removeInvoiceItem(cbaItemPreInvoicePlugins);
}
// Use credit after we call the plugin (https://github.com/killbill/killbill/issues/637)
final InvoiceItem cbaItemPostInvoicePlugins = computeCBAOnExistingInvoice(invoice, internalCallContext);
if (cbaItemPostInvoicePlugins != null) {
invoice.addInvoiceItem(cbaItemPostInvoicePlugins);
}
}
if (!isDryRun) {
// Compute whether this is a new invoice object (or just some adjustments on an existing invoice), and extract invoiceIds for later use
final Set<UUID> uniqueInvoiceIds = getUniqueInvoiceIds(invoice);
final boolean isRealInvoiceWithItems = uniqueInvoiceIds.remove(invoice.getId());
final Set<UUID> adjustedUniqueOtherInvoiceId = uniqueInvoiceIds;
logInvoiceWithItems(account, invoice, actualTargetDate, adjustedUniqueOtherInvoiceId, isRealInvoiceWithItems);
// Transformation to Invoice -> InvoiceModelDao
final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
final List<InvoiceItemModelDao> invoiceItemModelDaos = transformToInvoiceModelDao(invoice.getInvoiceItems());
invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
final Set<InvoiceTrackingModelDao> trackingIds = new HashSet<>();
for (final TrackingRecordId cur : invoiceWithMetadata.getTrackingIds()) {
trackingIds.add(new InvoiceTrackingModelDao(cur.getTrackingId(), cur.getInvoiceId(), cur.getSubscriptionId(), cur.getUnitType(), cur.getRecordDate()));
}
// Commit invoice on disk
final ExistingInvoiceMetadata existingInvoiceMetadata = new ExistingInvoiceMetadata(accountInvoices.getInvoices());
startNano = System.nanoTime();
commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, billingEvents, trackingIds, futureAccountNotifications, existingInvoiceMetadata, internalCallContext);
invoiceTimings.put(InvoiceTiming.COMMIT_INVOICE, System.nanoTime() - startNano);
startNano = System.nanoTime();
setChargedThroughDatesNoExceptions(invoiceWithMetadata.getChargeThroughDates(), internalCallContext);
invoiceTimings.put(InvoiceTiming.SET_CHARGE_THROUGH_DT, System.nanoTime() - startNano);
success = true;
}
} finally {
// Make sure we always set future notifications in case of errors
if (!isDryRun && !success) {
setFutureNotifications(account, futureAccountNotifications, internalCallContext);
}
if (isDryRun || success) {
final DefaultInvoice refreshedInvoice = isDryRun ? invoice : new DefaultInvoice(invoiceDao.getById(invoice.getId(), internalCallContext));
startNano = System.nanoTime();
invoicePluginDispatcher.onSuccessCall(actualTargetDate, refreshedInvoice, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
invoiceTimings.put(InvoiceTiming.PLUGINS_COMPLETION_CALL, System.nanoTime() - startNano);
} else {
startNano = System.nanoTime();
invoicePluginDispatcher.onFailureCall(actualTargetDate, invoice, accountInvoices.getInvoices(), isDryRun, isRescheduled, callContext, pluginProperties, internalCallContext);
invoiceTimings.put(InvoiceTiming.PLUGINS_COMPLETION_CALL, System.nanoTime() - startNano);
}
}
return new InvoiceWithFutureNotifications(invoice, futureAccountNotifications);
}
Aggregations