Skip to content

Commit

Permalink
#459 : fixing PR comments. New tests added. Updating parent invoice i…
Browse files Browse the repository at this point in the history
…tem when its from the same child account.
  • Loading branch information
matias-aguero-hs committed Feb 18, 2016
1 parent 35a9a16 commit 90195ea
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 22 deletions.
Expand Up @@ -27,12 +27,13 @@
import org.joda.time.LocalDate;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
Expand Down Expand Up @@ -379,7 +380,7 @@ public void testParentInvoiceGeneration() throws Exception {
// Move through time and verify new parent Invoice. No payments are expected.
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE,
NextEvent.INVOICE, NextEvent.INVOICE);
clock.addDays(31);
clock.addMonths(1);
assertListenerStatus();

// Second Parent invoice over Recurring period
Expand All @@ -402,4 +403,67 @@ public void testParentInvoiceGeneration() throws Exception {

}

@Test(groups = "slow")
public void testParentInvoiceGenerationMultipleActionsSameDay() throws Exception {

final int billingDay = 14;
final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);

log.info("Beginning test with BCD of " + billingDay);
final Account parentAccount = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
final Account childAccount = createAccountWithNonOsgiPaymentMethod(getChildAccountData(billingDay, parentAccount.getId(), true));

// set clock to the initial start date
clock.setTime(initialCreationDate);

//
// CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
//
DefaultEntitlement baseEntitlementChild = createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);

// Moving a day the NotificationQ calls the commitInvoice. No payment is expected because balance is 0
busHandler.pushExpectedEvents(NextEvent.INVOICE);
clock.addDays(1);
assertListenerStatus();

// Move through time and verify new parent Invoice. No payments are expected.
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE);
clock.addMonths(1);
assertListenerStatus();

// check parent Invoice with child plan amount
List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), callContext);
assertEquals(parentInvoices.size(), 2);

Invoice parentInvoice = parentInvoices.get(1);
assertEquals(parentInvoice.getNumberOfItems(), 1);
assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
assertTrue(parentInvoice.isParentInvoice());
assertEquals(parentInvoice.getBalance().toString(), "29.95");

// change plan
busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
final Entitlement newChildEntitlement = baseEntitlementChild.changePlanOverrideBillingPolicy("Shotgun", BillingPeriod.MONTHLY, baseEntitlementChild.getLastActivePriceList().getName(), null, clock.getToday(childAccount.getTimeZone()), BillingActionPolicy.IMMEDIATE, null, callContext);
assertListenerStatus();

// check parent invoice. Expected to have the same invoice item but the amount updated
parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), callContext);
assertEquals(parentInvoices.size(), 2);

parentInvoice = parentInvoices.get(1);
assertEquals(parentInvoice.getNumberOfItems(), 1);
assertEquals(parentInvoice.getStatus(), InvoiceStatus.DRAFT);
assertTrue(parentInvoice.isParentInvoice());
assertEquals(parentInvoice.getBalance().toString(), "235.29");

// Moving a day the NotificationQ calls the commitInvoice. Now payment is expected
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(1);
assertListenerStatus();

parentInvoice = invoiceUserApi.getInvoice(parentInvoice.getId(), callContext);
assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);

}

}
Expand Up @@ -66,7 +66,6 @@
import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
import org.killbill.billing.invoice.api.user.DefaultInvoiceNotificationInternalEvent;
import org.killbill.billing.invoice.api.user.DefaultNullInvoiceEvent;
import org.killbill.billing.invoice.calculator.InvoiceCalculatorUtils;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
Expand Down Expand Up @@ -706,30 +705,41 @@ public List<PlanPhasePriceOverride> getPlanPhasePriceOverrides() {
}
}

public void processParentInvoiceForInvoiceGeneration(final ImmutableAccountData account, final UUID invoiceId, final InternalCallContext context) throws InvoiceApiException {
public void processParentInvoiceForInvoiceGeneration(final ImmutableAccountData account, final UUID childInvoiceId, final InternalCallContext context) throws InvoiceApiException {

final InvoiceModelDao invoiceModelDao = invoiceDao.getById(invoiceId, context);
final Invoice invoice = new DefaultInvoice(invoiceModelDao);

// BigDecimal invoiceAmount = InvoiceCalculatorUtils.computeInvoiceBalance(invoice.getCurrency(), invoice.getInvoiceItems(), invoice.getPayments());
BigDecimal invoiceAmount = invoice.getChargedAmount();
InvoiceModelDao parentInvoice = invoiceDao.getParentDraftInvoice(account.getParentAccountId(), context);
final InvoiceModelDao childInvoiceModelDao = invoiceDao.getById(childInvoiceId, context);
final Invoice childInvoice = new DefaultInvoice(childInvoiceModelDao);

final Long parentAccountRecordId = internalCallContextFactory.getRecordIdFromObject(account.getParentAccountId(), ObjectType.ACCOUNT, buildTenantContext(context));
final InternalCallContext parentContext = new InternalCallContext(context, parentAccountRecordId);
final InternalCallContext parentContext = internalCallContextFactory.createInternalCallContext(parentAccountRecordId, context);

BigDecimal childInvoiceAmount = childInvoice.getChargedAmount();
InvoiceModelDao parentInvoice = invoiceDao.getParentDraftInvoice(account.getParentAccountId(), parentContext);

final DateTime today = clock.getNow(account.getTimeZone());
final String description = account.getExternalKey().concat(" summary");
if (parentInvoice != null) {
InvoiceItem invoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), account.getParentAccountId(), account.getId(), invoiceAmount, account.getCurrency(), description);
parentInvoice.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));

for (InvoiceItemModelDao item : parentInvoice.getInvoiceItems()) {
if ((item.getChildAccountId() != null) && item.getChildAccountId().equals(childInvoice.getAccountId())) {
// update child item amount for existing parent invoice item
BigDecimal newChildInvoiceAmount = childInvoiceAmount.add(item.getAmount());
invoiceDao.updateInvoiceItemAmount(item.getId(), newChildInvoiceAmount, parentContext);
return;
}
}

// new item when the parent invoices does not have this child item yet
final ParentInvoiceItem newParentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), account.getParentAccountId(), account.getId(), childInvoiceAmount, account.getCurrency(), description);
parentInvoice.addInvoiceItem(new InvoiceItemModelDao(newParentInvoiceItem));

List<InvoiceModelDao> invoices = new ArrayList<InvoiceModelDao>();
invoices.add(parentInvoice);
invoiceDao.createInvoices(invoices, parentContext);
} else {
parentInvoice = new InvoiceModelDao(account.getParentAccountId(), today.toLocalDate(), account.getCurrency(), InvoiceStatus.DRAFT, true);
InvoiceItem invoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), account.getParentAccountId(), account.getId(), invoiceAmount, account.getCurrency(), description);
parentInvoice.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));
InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), account.getParentAccountId(), account.getId(), childInvoiceAmount, account.getCurrency(), description);
parentInvoice.addInvoiceItem(new InvoiceItemModelDao(parentInvoiceItem));

// build account date time zone
final AccountDateAndTimeZoneContext accountDateTimeZone = new DefaultAccountDateAndTimeZoneContext(today, account.getTimeZone());
Expand All @@ -739,7 +749,7 @@ public void processParentInvoiceForInvoiceGeneration(final ImmutableAccountData
}

// save parent child invoice relation
final InvoiceParentChildModelDao invoiceRelation = new InvoiceParentChildModelDao(parentInvoice.getId(), invoiceId, account.getId());
final InvoiceParentChildModelDao invoiceRelation = new InvoiceParentChildModelDao(parentInvoice.getId(), childInvoiceId, account.getId());
invoiceDao.createParentChildInvoiceRelation(invoiceRelation, parentContext);

}
Expand Down
Expand Up @@ -1019,7 +1019,31 @@ public InvoiceModelDao getParentDraftInvoice(final UUID parentAccountId, final I
@Override
public InvoiceModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceSqlDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
return invoiceSqlDao.getParentDraftInvoice(parentAccountId.toString(), context);
InvoiceModelDao invoice = invoiceSqlDao.getParentDraftInvoice(parentAccountId.toString(), context);
if (invoice != null) {
invoiceDaoHelper.populateChildren(invoice, entitySqlDaoWrapperFactory, context);
}
return invoice;
}
});
}

@Override
public void updateInvoiceItemAmount(final UUID invoiceItemId, final BigDecimal amount, final InternalCallContext context) throws InvoiceApiException {
transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<Void>() {
@Override
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceItemSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);

// Retrieve the invoice and make sure it belongs to the right account
final InvoiceItemModelDao invoiceItem = transactional.getById(invoiceItemId.toString(), context);

if (invoiceItem == null ) {
throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
}

transactional.updateAmount(invoiceItemId.toString(), amount, context);
return null;
}
});
}
Expand Down
Expand Up @@ -146,7 +146,7 @@ InvoicePaymentModelDao createRefund(UUID paymentId, BigDecimal amount, boolean i
* @param invoiceId the invoice id
* @param newState the new invoice state
* @param context the tenant context
* @throws InvoiceApiException
* @throws InvoiceApiException if any unexpected error occurs
*/
void changeInvoiceStatus(UUID invoiceId, InvoiceStatus newState, InternalCallContext context) throws InvoiceApiException;

Expand All @@ -155,7 +155,7 @@ InvoicePaymentModelDao createRefund(UUID paymentId, BigDecimal amount, boolean i
*
* @param invoiceRelation the invoice relation object
* @param context the tenant context
* @throws InvoiceApiException
* @throws InvoiceApiException if any unexpected error occurs
*/
void createParentChildInvoiceRelation(final InvoiceParentChildModelDao invoiceRelation, final InternalCallContext context) throws InvoiceApiException;

Expand All @@ -164,7 +164,8 @@ InvoicePaymentModelDao createRefund(UUID paymentId, BigDecimal amount, boolean i
*
* @param parentInvoiceId the parent invoice id
* @param context the tenant context
* @throws InvoiceApiException
* @return a list of parent-children relation
* @throws InvoiceApiException if any unexpected error occurs
*/
List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(UUID parentInvoiceId, final InternalCallContext context) throws InvoiceApiException;

Expand All @@ -174,7 +175,18 @@ InvoicePaymentModelDao createRefund(UUID paymentId, BigDecimal amount, boolean i
*
* @param parentAccountId the parent account id
* @param context the tenant context
* @return
* @return a parent invoice in DRAFT status
* @throws InvoiceApiException if any unexpected error occurs
*/
InvoiceModelDao getParentDraftInvoice(UUID parentAccountId, InternalCallContext context) throws InvoiceApiException;

/**
* Update invoice item amount
*
* @param invoiceItemId the invoice item id
* @param amount the new amount value
* @param context the tenant context
* @throws InvoiceApiException if any unexpected error occurs
*/
void updateInvoiceItemAmount(UUID invoiceItemId, BigDecimal amount, InternalCallContext context) throws InvoiceApiException;
}
Expand Up @@ -16,8 +16,12 @@

package org.killbill.billing.invoice.dao;

import java.math.BigDecimal;
import java.util.List;

import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.util.audit.ChangeType;
import org.killbill.billing.util.entity.dao.Audited;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
Expand All @@ -26,6 +30,7 @@
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.entity.dao.EntitySqlDao;
import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;

@EntitySqlDaoStringTemplate
public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItemModelDao, InvoiceItem> {
Expand All @@ -42,4 +47,10 @@ List<InvoiceItemModelDao> getInvoiceItemsBySubscription(@Bind("subscriptionId")
@SqlQuery
List<InvoiceItemModelDao> getAdjustedOrRepairedInvoiceItemsByLinkedId(@Bind("linkedItemId") final String linkedItemId,
@BindBean final InternalTenantContext context);

@SqlUpdate
@Audited(ChangeType.UPDATE)
void updateAmount(@Bind("id") String invoiceItemId,
@Bind("amount")BigDecimal amount,
@BindBean final InternalCallContext context);
}
Expand Up @@ -68,4 +68,11 @@ getAdjustedOrRepairedInvoiceItemsByLinkedId() ::= <<
AND type IN ('ITEM_ADJ', 'REPAIR_ADJ')
<AND_CHECK_TENANT()>
;
>>

updateAmount() ::= <<
UPDATE <tableName()>
SET amount = :amount
WHERE id = :id
<AND_CHECK_TENANT()>;
>>
Expand Up @@ -33,4 +33,5 @@ getChildInvoicesByParentInvoiceId() ::= <<
FROM <tableName()>
WHERE parent_invoice_id = :parentInvoiceId
<AND_CHECK_TENANT()>
<defaultOrderBy()>
>>
Expand Up @@ -68,4 +68,5 @@ getParentDraftInvoice() ::= <<
WHERE account_id = :accountId
AND status = 'DRAFT'
<AND_CHECK_TENANT()>
<defaultOrderBy()>
>>
Expand Up @@ -382,4 +382,9 @@ public InvoiceModelDao getParentDraftInvoice(final UUID parentAccountId, final I
public List<InvoiceParentChildModelDao> getChildInvoicesByParentInvoiceId(final UUID parentInvoiceId, final InternalCallContext context) throws InvoiceApiException {
throw new UnsupportedOperationException();
}

@Override
public void updateInvoiceItemAmount(final UUID invoiceItemId, final BigDecimal amount, final InternalCallContext context) throws InvoiceApiException {
throw new UnsupportedOperationException();
}
}
Expand Up @@ -45,6 +45,7 @@
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.entity.EntityPersistenceException;
import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
import org.killbill.billing.invoice.MockBillingEventSet;
import org.killbill.billing.invoice.api.Invoice;
Expand All @@ -61,13 +62,16 @@
import org.killbill.billing.invoice.model.DefaultInvoicePayment;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
import org.killbill.billing.invoice.model.ParentInvoiceItem;
import org.killbill.billing.invoice.model.RecurringInvoiceItem;
import org.killbill.billing.invoice.model.RepairAdjInvoiceItem;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.util.AccountDateAndTimeZoneContext;
import org.killbill.billing.util.currency.KillBillMoney;
import org.killbill.billing.util.timezone.DefaultAccountDateAndTimeZoneContext;
import org.killbill.clock.ClockMock;
import org.mockito.Mockito;
import org.skife.jdbi.v2.exceptions.TransactionFailedException;
Expand Down Expand Up @@ -1742,4 +1746,28 @@ public void testCreateParentChildInvoiceRelation() throws InvoiceApiException {

}

@Test(groups = "slow")
public void testCreateParentInvoice() throws InvoiceApiException {

final UUID parentAccountId = UUID.randomUUID();
final UUID childAccountId = UUID.randomUUID();
final DateTime today = clock.getNow(account.getTimeZone());

InvoiceModelDao parentInvoice = new InvoiceModelDao(parentAccountId, today.toLocalDate(), account.getCurrency(), InvoiceStatus.DRAFT, true);
InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), parentAccountId, childAccountId, BigDecimal.TEN, account.getCurrency(), "");
parentInvoice.addInvoiceItem(new InvoiceItemModelDao(parentInvoiceItem));

// build account date time zone
final AccountDateAndTimeZoneContext accountDateTimeZone = new DefaultAccountDateAndTimeZoneContext(today, account.getTimeZone());
final FutureAccountNotifications futureAccountNotifications = new FutureAccountNotifications(accountDateTimeZone, null);
invoiceDao.createInvoice(parentInvoice, parentInvoice.getInvoiceItems(), true, futureAccountNotifications, context);

final InvoiceModelDao parentDraftInvoice = invoiceDao.getParentDraftInvoice(parentAccountId, context);

assertNotNull(parentDraftInvoice);
assertEquals(parentDraftInvoice.getStatus(), InvoiceStatus.DRAFT);
assertEquals(parentDraftInvoice.getInvoiceItems().size(), 1);

}

}

0 comments on commit 90195ea

Please sign in to comment.