use of org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory 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);
final Set<UUID> invoiceIds = new HashSet<>();
transactionalSqlDao.execute(false, InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
// Retrieve the invoice and make sure it belongs to the right account
final InvoiceModelDao invoice = invoiceSqlDao.getById(invoiceId.toString(), context);
if (invoice == null || !invoice.getAccountId().equals(accountId) || invoice.isMigrated() || invoice.getStatus() != InvoiceStatus.COMMITTED) {
throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
}
invoiceDaoHelper.populateChildren(invoice, invoicesTags, entitySqlDaoWrapperFactory, context);
// 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);
}
if (cbaItem.getAmount().compareTo(BigDecimal.ZERO) < 0) {
/* Credit consumption */
invoiceItemSqlDao.updateItemFields(cbaItem.getId().toString(), BigDecimal.ZERO, "Delete used credit", null, context);
invoiceIds.add(invoice.getId());
} else if (cbaItem.getAmount().compareTo(BigDecimal.ZERO) > 0) {
/* Credit generation */
final InvoiceItemModelDao creditItem = Iterables.tryFind(invoice.getInvoiceItems(), new Predicate<InvoiceItemModelDao>() {
@Override
public boolean apply(final InvoiceItemModelDao targetItem) {
return targetItem.getType() == InvoiceItemType.CREDIT_ADJ && targetItem.getAmount().negate().compareTo(cbaItem.getAmount()) >= 0;
}
}).orNull();
// In case this is a credit invoice (pure credit, or mixed), we allow to 'delete' credit generation
if (creditItem != null) {
/* Credit Invoice */
final BigDecimal accountCBA = cbaDao.getAccountCBAFromTransaction(entitySqlDaoWrapperFactory, context);
// If we don't have enough credit left on the account, we reclaim what is necessary
if (accountCBA.compareTo(cbaItem.getAmount()) < 0) {
final BigDecimal amountToReclaim = cbaItem.getAmount().subtract(accountCBA);
final BigDecimal reclaimed = reclaimCreditFromTransaction(accountId, amountToReclaim, invoiceIds, entitySqlDaoWrapperFactory, context);
Preconditions.checkState(reclaimed.compareTo(amountToReclaim) == 0, String.format("Unexpected state, reclaimed used credit [%s/%s]", reclaimed, amountToReclaim));
}
invoiceItemSqlDao.updateItemFields(cbaItem.getId().toString(), BigDecimal.ZERO, "Delete gen credit", null, context);
final BigDecimal adjustedCreditAmount = creditItem.getAmount().add(cbaItem.getAmount());
invoiceItemSqlDao.updateItemFields(creditItem.getId().toString(), adjustedCreditAmount, "Delete gen credit", null, context);
invoiceIds.add(invoice.getId());
} else /* System generated credit, e.g Repair invoice */
{
// TODO Add missing error https://github.com/killbill/killbill/issues/1501
throw new IllegalStateException("Cannot delete system generated credit");
}
}
for (final UUID invoiceId : invoiceIds) {
notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
}
return null;
}
});
}
use of org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory in project killbill by killbill.
the class DefaultInvoiceDao method createInvoices.
private List<InvoiceItemModelDao> createInvoices(final Iterable<InvoiceModelDao> invoices, @Nullable final BillingEventSet billingEvents, final Set<InvoiceTrackingModelDao> trackingIds, final FutureAccountNotifications callbackDateTimePerSubscriptions, @Nullable final ExistingInvoiceMetadata existingInvoiceMetadataOrNull, final boolean returnCreatedInvoiceItems, final InternalCallContext context) {
// Track invoices that are being created
final Set<UUID> createdInvoiceIds = new HashSet<UUID>();
// Track invoices that already exist but are being committed -- AUTO_INVOICING_REUSE_DRAFT mode
final Set<UUID> committedReusedInvoiceId = new HashSet<UUID>();
// Track all invoices that are referenced through all invoiceItems
final Set<UUID> allInvoiceIds = new HashSet<UUID>();
// Track invoices that are committed but were not created or reused -- to sent the InvoiceAdjustment bus event
final Set<UUID> adjustedCommittedInvoiceIds = new HashSet<UUID>();
final Collection<UUID> invoiceIdsReferencedFromItems = new HashSet<UUID>();
for (final InvoiceModelDao invoiceModelDao : invoices) {
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceModelDao.getInvoiceItems()) {
invoiceIdsReferencedFromItems.add(invoiceItemModelDao.getInvoiceId());
}
}
if (Iterables.isEmpty(invoices)) {
return ImmutableList.<InvoiceItemModelDao>of();
}
final UUID accountId = invoices.iterator().next().getAccountId();
final List<Tag> invoicesTags = getInvoicesTags(context);
final Map<UUID, InvoiceModelDao> invoiceByInvoiceId = new HashMap<UUID, InvoiceModelDao>();
return transactionalSqlDao.execute(false, new EntitySqlDaoTransactionWrapper<List<InvoiceItemModelDao>>() {
@Override
public List<InvoiceItemModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
final InvoiceItemSqlDao transInvoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
final InvoiceBillingEventSqlDao billingEventSqlDao = entitySqlDaoWrapperFactory.become(InvoiceBillingEventSqlDao.class);
final ExistingInvoiceMetadata existingInvoiceMetadata;
if (existingInvoiceMetadataOrNull == null) {
existingInvoiceMetadata = new ExistingInvoiceMetadata(invoiceSqlDao, transInvoiceItemSqlDao);
} else {
existingInvoiceMetadata = existingInvoiceMetadataOrNull;
}
final List<InvoiceItemModelDao> invoiceItemsToCreate = new LinkedList<InvoiceItemModelDao>();
for (final InvoiceModelDao invoiceModelDao : invoices) {
invoiceByInvoiceId.put(invoiceModelDao.getId(), invoiceModelDao);
final boolean isNotShellInvoice = invoiceIdsReferencedFromItems.remove(invoiceModelDao.getId());
final InvoiceModelDao invoiceOnDisk = existingInvoiceMetadata.getExistingInvoice(invoiceModelDao.getId(), context);
if (isNotShellInvoice) {
// Create the invoice if this is not a shell invoice and it does not already exist
if (invoiceOnDisk == null) {
createAndRefresh(invoiceSqlDao, invoiceModelDao, context);
if (billingEvents != null) {
billingEventSqlDao.create(new InvoiceBillingEventModelDao(invoiceModelDao.getId(), BillingEventSerializer.serialize(billingEvents), context.getCreatedDate()), context);
}
createdInvoiceIds.add(invoiceModelDao.getId());
} else {
// Allow transition from DRAFT to COMMITTED or keep current status
InvoiceStatus newStatus = invoiceOnDisk.getStatus();
boolean statusUpdated = false;
if (InvoiceStatus.COMMITTED == invoiceModelDao.getStatus() && InvoiceStatus.DRAFT == invoiceOnDisk.getStatus()) {
statusUpdated = true;
newStatus = InvoiceStatus.COMMITTED;
}
// Update if target date is specified and prev targetDate was either null or prior to new date
LocalDate newTargetDate = invoiceOnDisk.getTargetDate();
boolean targetDateUpdated = false;
if (invoiceModelDao.getTargetDate() != null && (invoiceOnDisk.getTargetDate() == null || invoiceOnDisk.getTargetDate().compareTo(invoiceModelDao.getTargetDate()) < 0)) {
targetDateUpdated = true;
newTargetDate = invoiceModelDao.getTargetDate();
}
if (statusUpdated || targetDateUpdated) {
invoiceSqlDao.updateStatusAndTargetDate(invoiceModelDao.getId().toString(), newStatus.toString(), newTargetDate, context);
committedReusedInvoiceId.add(invoiceModelDao.getId());
}
}
}
// Create the invoice items if needed (note: they may not necessarily belong to that invoice)
for (final InvoiceItemModelDao invoiceItemModelDao : invoiceModelDao.getInvoiceItems()) {
final InvoiceItemModelDao existingInvoiceItem = existingInvoiceMetadata.getExistingInvoiceItem(invoiceItemModelDao.getId(), context);
// Also for ALLOWED_INVOICE_ITEM_TYPES, we expect plugins to potentially modify the amount
if (existingInvoiceItem == null) {
invoiceItemsToCreate.add(invoiceItemModelDao);
allInvoiceIds.add(invoiceItemModelDao.getInvoiceId());
} else if (InvoicePluginDispatcher.ALLOWED_INVOICE_ITEM_TYPES.contains(invoiceItemModelDao.getType()) && // items would not be re-written
(invoiceItemModelDao.getAmount().compareTo(existingInvoiceItem.getAmount()) != 0)) {
if (checkAgainstExistingInvoiceItemState(existingInvoiceItem, invoiceItemModelDao)) {
transInvoiceItemSqlDao.updateItemFields(invoiceItemModelDao.getId().toString(), invoiceItemModelDao.getAmount(), invoiceItemModelDao.getDescription(), invoiceItemModelDao.getItemDetails(), context);
}
}
}
final boolean wasInvoiceCreatedOrCommitted = createdInvoiceIds.contains(invoiceModelDao.getId()) || committedReusedInvoiceId.contains(invoiceModelDao.getId());
final boolean hasInvoiceBeenAdjusted = allInvoiceIds.contains(invoiceModelDao.getId());
if (InvoiceStatus.COMMITTED.equals(invoiceModelDao.getStatus())) {
if (wasInvoiceCreatedOrCommitted) {
notifyBusOfInvoiceCreation(entitySqlDaoWrapperFactory, invoiceModelDao, context);
} else if (hasInvoiceBeenAdjusted) {
adjustedCommittedInvoiceIds.add(invoiceModelDao.getId());
}
} else if (wasInvoiceCreatedOrCommitted && invoiceModelDao.isParentInvoice()) {
notifyOfParentInvoiceCreation(entitySqlDaoWrapperFactory, invoiceModelDao, context);
}
// We always add the future notifications when the callbackDateTimePerSubscriptions is not empty (incl. DRAFT invoices containing RECURRING items created using AUTO_INVOICING_DRAFT feature)
notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoiceModelDao.getAccountId(), callbackDateTimePerSubscriptions, context);
}
// Bulk insert the invoice items
createInvoiceItemsFromTransaction(transInvoiceItemSqlDao, invoiceItemsToCreate, context);
// CBA COMPLEXITY...
//
// Optimized path where we don't need to refresh invoices
final CBALogicWrapper cbaWrapper = new CBALogicWrapper(accountId, invoicesTags, context, entitySqlDaoWrapperFactory);
if (createdInvoiceIds.equals(allInvoiceIds)) {
final List<InvoiceModelDao> cbaInvoicesInput = new ArrayList<>();
for (final UUID id : createdInvoiceIds) {
cbaInvoicesInput.add(invoiceByInvoiceId.get(id));
}
cbaWrapper.runCBALogicWithNotificationEvents(adjustedCommittedInvoiceIds, createdInvoiceIds, cbaInvoicesInput);
} else {
cbaWrapper.runCBALogicWithNotificationEvents(adjustedCommittedInvoiceIds, createdInvoiceIds, allInvoiceIds);
}
if (trackingIds != null && !trackingIds.isEmpty()) {
final InvoiceTrackingSqlDao trackingIdsSqlDao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
trackingIdsSqlDao.create(trackingIds, context);
}
if (returnCreatedInvoiceItems) {
if (invoiceItemsToCreate.isEmpty()) {
return ImmutableList.<InvoiceItemModelDao>of();
} else {
return transInvoiceItemSqlDao.getByIds(Collections2.<InvoiceItemModelDao, String>transform(invoiceItemsToCreate, new Function<InvoiceItemModelDao, String>() {
@Override
public String apply(final InvoiceItemModelDao input) {
return input.getId().toString();
}
}), context);
}
} else {
return null;
}
}
});
}
use of org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory in project killbill by killbill.
the class TestSubscriptionDao method testBundleExternalKeyTransferred.
@Test(groups = "slow")
public void testBundleExternalKeyTransferred() throws Exception {
final String externalKey = "2534125sdfsd";
final DateTime startDate = clock.getUTCNow();
final DateTime createdDate = startDate.plusSeconds(10);
final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
final SubscriptionBaseBundle bundle = dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
final List<SubscriptionBaseBundle> result = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
final List<AuditLog> auditLogsBeforeRenaming = auditUserApi.getAuditLogs(bundle.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
assertEquals(auditLogsBeforeRenaming.size(), 1);
assertEquals(auditLogsBeforeRenaming.get(0).getChangeType(), ChangeType.INSERT);
// Update key to 'internal KB value 'kbtsf-12345:'
dao.updateBundleExternalKey(bundle.getId(), "kbtsf-12345:" + bundle.getExternalKey(), internalCallContext);
final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result2.size(), 1);
final List<AuditLog> auditLogsAfterRenaming = auditUserApi.getAuditLogs(bundle.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
assertEquals(auditLogsAfterRenaming.size(), 2);
assertEquals(auditLogsAfterRenaming.get(0).getChangeType(), ChangeType.INSERT);
assertEquals(auditLogsAfterRenaming.get(1).getChangeType(), ChangeType.UPDATE);
// Create new bundle with original key, verify all results show original key, stripping down internal prefix
final DefaultSubscriptionBaseBundle bundleDef2 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
final SubscriptionBaseBundle bundle2 = dao.createSubscriptionBundle(bundleDef2, catalog, true, internalCallContext);
final List<SubscriptionBaseBundle> result3 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result3.size(), 2);
assertEquals(result3.get(0).getId(), bundle.getId());
assertEquals(result3.get(0).getExternalKey(), bundle2.getExternalKey());
assertEquals(result3.get(1).getId(), bundle2.getId());
assertEquals(result3.get(1).getExternalKey(), bundle2.getExternalKey());
final List<AuditLog> auditLogs2BeforeRenaming = auditUserApi.getAuditLogs(bundle2.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
assertEquals(auditLogs2BeforeRenaming.size(), 1);
assertEquals(auditLogs2BeforeRenaming.get(0).getChangeType(), ChangeType.INSERT);
// This time we call the lower SqlDao to rename the bundle automatically and verify we still get same # results,
// with original key
transactionalSqlDao.execute(false, new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
entitySqlDaoWrapperFactory.become(BundleSqlDao.class).renameBundleExternalKey(ImmutableList.<String>of(bundle2.getId().toString()), "foo", internalCallContext);
return null;
}
});
final List<SubscriptionBaseBundle> result4 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result4.size(), 2);
assertEquals(result4.get(0).getExternalKey(), bundle2.getExternalKey());
assertEquals(result4.get(0).getId(), bundle.getId());
assertEquals(result4.get(1).getExternalKey(), bundle2.getExternalKey());
assertEquals(result4.get(1).getId(), bundle2.getId());
final List<AuditLog> auditLogs2AfterRenaming = auditUserApi.getAuditLogs(bundle2.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
assertEquals(auditLogs2AfterRenaming.size(), 2);
assertEquals(auditLogs2AfterRenaming.get(0).getChangeType(), ChangeType.INSERT);
assertEquals(auditLogs2AfterRenaming.get(1).getChangeType(), ChangeType.UPDATE);
// Create bundle one more time
final DefaultSubscriptionBaseBundle bundleDef3 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
final SubscriptionBaseBundle bundle3 = dao.createSubscriptionBundle(bundleDef3, catalog, true, internalCallContext);
final List<SubscriptionBaseBundle> result5 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result5.size(), 3);
assertEquals(result5.get(0).getExternalKey(), bundle2.getExternalKey());
assertEquals(result5.get(1).getExternalKey(), bundle2.getExternalKey());
assertEquals(result5.get(2).getExternalKey(), bundle2.getExternalKey());
}
use of org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory in project killbill by killbill.
the class DefaultSubscriptionDao method undoOperation.
private void undoOperation(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> inputEvents, final ApiEventType targetOperation, final SubscriptionBaseTransitionType transitionType, final InternalCallContext context) {
final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
transactionalSqlDao.execute(false, new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
final UUID subscriptionId = subscription.getId();
Set<SubscriptionEventModelDao> targetEvents = new HashSet<SubscriptionEventModelDao>();
final Date now = context.getCreatedDate().toDate();
final List<SubscriptionEventModelDao> eventModels = transactional.getFutureActiveEventForSubscription(subscriptionId.toString(), now, contextWithUpdatedDate);
for (final SubscriptionEventModelDao cur : eventModels) {
if (cur.getEventType() == EventType.API_USER && cur.getUserType() == targetOperation) {
targetEvents.add(cur);
} else if (cur.getEventType() == EventType.PHASE) {
targetEvents.add(cur);
}
}
if (!targetEvents.isEmpty()) {
for (SubscriptionEventModelDao target : targetEvents) {
transactional.unactiveEvent(target.getId().toString(), contextWithUpdatedDate);
}
for (final SubscriptionBaseEvent cur : inputEvents) {
transactional.create(new SubscriptionEventModelDao(cur), contextWithUpdatedDate);
recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory, cur.getEffectiveDate(), new SubscriptionNotificationKey(cur.getId()), contextWithUpdatedDate);
}
// Notify the Bus of the latest requested change
notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, inputEvents.get(inputEvents.size() - 1), transitionType, 0, contextWithUpdatedDate);
}
return null;
}
});
}
use of org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory in project killbill by killbill.
the class DefaultSubscriptionDao method updateChargedThroughDates.
@Override
public void updateChargedThroughDates(final Map<DateTime, List<UUID>> chargeThroughDates, final InternalCallContext context) {
final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
transactionalSqlDao.execute(false, new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionSqlDao transactionalDao = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
for (final Map.Entry<DateTime, List<UUID>> kv : chargeThroughDates.entrySet()) {
transactionalDao.updateChargedThroughDates(kv.getValue(), kv.getKey().toDate(), contextWithUpdatedDate);
}
return null;
}
});
}
Aggregations