diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
index 08db235..3bb88f0 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
@@ -88,10 +88,10 @@ public class AuditedInvoiceDao implements InvoiceDao {
@Inject
public AuditedInvoiceDao(final IDBI dbi,
- final NextBillingDatePoster nextBillingDatePoster,
- final TagUserApi tagUserApi,
- final Clock clock,
- final Bus eventBus) {
+ final NextBillingDatePoster nextBillingDatePoster,
+ final TagUserApi tagUserApi,
+ final Clock clock,
+ final Bus eventBus) {
this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
@@ -355,9 +355,12 @@ public class AuditedInvoiceDao implements InvoiceDao {
@Override
public InvoicePayment createRefund(final UUID paymentId, final BigDecimal requestedRefundAmount, final boolean isInvoiceAdjusted,
- final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final UUID paymentCookieId,
- final CallContext context)
- throws InvoiceApiException {
+ final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final UUID paymentCookieId,
+ final CallContext context)
+ throws InvoiceApiException {
+
+ final boolean isInvoiceItemAdjusted = isInvoiceAdjusted && invoiceItemIdsWithNullAmounts.size() > 0;
+
return invoicePaymentSqlDao.inTransaction(new Transaction<InvoicePayment, InvoicePaymentSqlDao>() {
@Override
public InvoicePayment inTransaction(final InvoicePaymentSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -371,8 +374,8 @@ public class AuditedInvoiceDao implements InvoiceDao {
// Retrieve the amounts to adjust, if needed
final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = computeItemAdjustments(payment.getInvoiceId().toString(),
- transInvoiceDao,
- invoiceItemIdsWithNullAmounts);
+ transInvoiceDao,
+ invoiceItemIdsWithNullAmounts);
// Compute the actual amount to refund
final BigDecimal requestedPositiveAmount = computePositiveRefundAmount(payment, requestedRefundAmount, invoiceItemIdsWithAmounts);
@@ -385,8 +388,8 @@ public class AuditedInvoiceDao implements InvoiceDao {
}
final InvoicePayment refund = new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.REFUND, paymentId,
- payment.getInvoiceId(), context.getCreatedDate(), requestedPositiveAmount.negate(),
- payment.getCurrency(), paymentCookieId, payment.getId());
+ payment.getInvoiceId(), context.getCreatedDate(), requestedPositiveAmount.negate(),
+ payment.getCurrency(), paymentCookieId, payment.getId());
transactional.create(refund, context);
// Retrieve invoice after the Refund
@@ -404,7 +407,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
// At this point, we created the refund which made the invoice balance positive and applied any existing
// available CBA to that invoice.
// We now need to adjust the invoice and/or invoice items if needed and specified.
- if (isInvoiceAdjusted && invoiceItemIdsWithAmounts.size() == 0) {
+ if (isInvoiceAdjusted && !isInvoiceItemAdjusted) {
// Invoice adjustment
final BigDecimal maxBalanceToAdjust = (invoiceBalanceAfterRefund.compareTo(BigDecimal.ZERO) <= 0) ? BigDecimal.ZERO : invoiceBalanceAfterRefund;
final BigDecimal requestedPositiveAmountToAdjust = requestedPositiveAmount.compareTo(maxBalanceToAdjust) > 0 ? maxBalanceToAdjust : requestedPositiveAmount;
@@ -417,7 +420,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
for (final UUID invoiceItemId : invoiceItemIdsWithAmounts.keySet()) {
final BigDecimal adjAmount = invoiceItemIdsWithAmounts.get(invoiceItemId);
final InvoiceItem item = createAdjustmentItem(transInvoiceDao, invoice.getId(), invoiceItemId, adjAmount,
- invoice.getCurrency(), context.getCreatedDate().toLocalDate());
+ invoice.getCurrency(), context.getCreatedDate().toLocalDate());
transInvoiceItemDao.create(item, context);
}
}
@@ -443,7 +446,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
* @throws InvoiceApiException
*/
private Map<UUID, BigDecimal> computeItemAdjustments(final String invoiceId, final InvoiceSqlDao transInvoiceDao,
- final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts) throws InvoiceApiException {
+ final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts) throws InvoiceApiException {
// Populate the missing amounts for individual items, if needed
final Builder<UUID, BigDecimal> invoiceItemIdsWithAmountsBuilder = new Builder<UUID, BigDecimal>();
if (invoiceItemIdsWithNullAmounts.size() == 0) {
@@ -460,13 +463,41 @@ public class AuditedInvoiceDao implements InvoiceDao {
for (final UUID invoiceItemId : invoiceItemIdsWithNullAmounts.keySet()) {
final BigDecimal adjAmount = Objects.firstNonNull(invoiceItemIdsWithNullAmounts.get(invoiceItemId),
- getInvoiceItemAmountForId(invoice, invoiceItemId));
- invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, adjAmount);
+ getInvoiceItemAmountForId(invoice, invoiceItemId));
+ final BigDecimal adjAmountRemainingAfterRepair = computeItemAdjustmentAmount(invoiceItemId, adjAmount, invoice.getInvoiceItems());
+ if (adjAmountRemainingAfterRepair.compareTo(BigDecimal.ZERO) > 0) {
+ invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, adjAmountRemainingAfterRepair);
+ }
}
return invoiceItemIdsWithAmountsBuilder.build();
}
+ /**
+ *
+ * @param invoiceItem item we are adjusting
+ * @param requestedPositiveAmountToAdjust amount we are adjusting for that item
+ * @param invoiceItems list of all invoice items on this invoice
+ * @return the amount we should really adjust based on whether or not the item got repaired
+ */
+ private BigDecimal computeItemAdjustmentAmount(final UUID invoiceItem, final BigDecimal requestedPositiveAmountToAdjust, final List<InvoiceItem> invoiceItems) {
+
+ BigDecimal positiveRepairedAmount = BigDecimal.ZERO;
+
+ final Collection<InvoiceItem> repairedItems = Collections2.filter(invoiceItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(@Nullable InvoiceItem input) {
+ return (input.getInvoiceItemType() == InvoiceItemType.REPAIR_ADJ && input.getLinkedItemId().equals(invoiceItem));
+ }
+ });
+ for (InvoiceItem cur : repairedItems) {
+ // Repair item are negative so we negate to make it positive
+ positiveRepairedAmount = positiveRepairedAmount.add(cur.getAmount().negate());
+ }
+ return (positiveRepairedAmount.compareTo(requestedPositiveAmountToAdjust) >= 0) ? BigDecimal.ZERO : requestedPositiveAmountToAdjust.subtract(positiveRepairedAmount);
+ }
+
+
private BigDecimal getInvoiceItemAmountForId(final Invoice invoice, final UUID invoiceItemId) throws InvoiceApiException {
for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
if (invoiceItem.getId().equals(invoiceItemId)) {
@@ -520,7 +551,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_NOT_FOUND, invoicePaymentId.toString());
} else {
final InvoicePayment chargeBack = new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.CHARGED_BACK, payment.getPaymentId(),
- payment.getInvoiceId(), context.getCreatedDate(), requestedChargedBackAmout.negate(), payment.getCurrency(), null, payment.getId());
+ payment.getInvoiceId(), context.getCreatedDate(), requestedChargedBackAmout.negate(), payment.getCurrency(), null, payment.getId());
transactional.create(chargeBack, context);
// Add audit
@@ -580,8 +611,8 @@ public class AuditedInvoiceDao implements InvoiceDao {
@Override
public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId, final String description,
- final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context)
- throws InvoiceApiException {
+ final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context)
+ throws InvoiceApiException {
return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
@Override
public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -594,8 +625,8 @@ public class AuditedInvoiceDao implements InvoiceDao {
}
final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceIdForExternalCharge, accountId,
- bundleId, description,
- effectiveDate, amount, currency);
+ bundleId, description,
+ effectiveDate, amount, currency);
final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
transInvoiceItemDao.create(externalCharge, context);
@@ -628,7 +659,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
@Override
public InvoiceItem insertCredit(final UUID accountId, @Nullable final UUID invoiceId, final BigDecimal positiveCreditAmount,
- final LocalDate effectiveDate, final Currency currency, final CallContext context) {
+ final LocalDate effectiveDate, final Currency currency, final CallContext context) {
return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
@Override
public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -658,13 +689,13 @@ public class AuditedInvoiceDao implements InvoiceDao {
@Override
public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
- final LocalDate effectiveDate, @Nullable final BigDecimal positiveAdjAmount,
- @Nullable final Currency currency, final CallContext context) {
+ final LocalDate effectiveDate, @Nullable final BigDecimal positiveAdjAmount,
+ @Nullable final Currency currency, final CallContext context) {
return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
@Override
public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
final InvoiceItem invoiceItemAdjustment = createAdjustmentItem(transactional, invoiceId, invoiceItemId, positiveAdjAmount,
- currency, effectiveDate);
+ currency, effectiveDate);
insertItemAndAddCBAIfNeeded(transactional, invoiceItemAdjustment, context);
notifyBusOfInvoiceAdjustment(transactional, invoiceId, accountId, context.getUserToken());
@@ -694,7 +725,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
// First, adjust the same invoice with the CBA amount to "delete"
final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
- cbaItem.getId(), cbaItem.getAmount().negate(), cbaItem.getCurrency());
+ cbaItem.getId(), cbaItem.getAmount().negate(), cbaItem.getCurrency());
invoiceItemSqlDao.create(cbaAdjItem, context);
// If there is more account credit than CBA we adjusted, we're done.
@@ -705,7 +736,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
}
final List<Invoice> invoicesFollowing = transactional.getInvoicesByAccountAfterDate(accountId.toString(),
- invoice.getInvoiceDate().toDateTimeAtStartOfDay().toDate());
+ invoice.getInvoiceDate().toDateTimeAtStartOfDay().toDate());
populateChildren(invoicesFollowing, transactional);
// The remaining amount to adjust (i.e. the amount of credits used on following invoices)
@@ -723,7 +754,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
for (final InvoiceItem cbaUsed : Lists.reverse(invoiceFollowing.getInvoiceItems())) {
// Ignore non CBA items or credits (CBA >= 0)
if (!InvoiceItemType.CBA_ADJ.equals(cbaUsed.getInvoiceItemType()) ||
- cbaUsed.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+ cbaUsed.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
continue;
}
@@ -745,7 +776,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
// Add the adjustment on that invoice
final InvoiceItem nextCBAAdjItem = new CreditBalanceAdjInvoiceItem(invoiceFollowing.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
- cbaItem.getId(), positiveCBAAdjItemAmount, cbaItem.getCurrency());
+ cbaItem.getId(), positiveCBAAdjItemAmount, cbaItem.getCurrency());
invoiceItemSqlDao.create(nextCBAAdjItem, context);
if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
break;
@@ -774,7 +805,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
* @return the adjustment item
*/
private InvoiceItem createAdjustmentItem(final InvoiceSqlDao transactional, final UUID invoiceId, final UUID invoiceItemId,
- final BigDecimal positiveAdjAmount, final Currency currency, final LocalDate effectiveDate) throws InvoiceApiException {
+ final BigDecimal positiveAdjAmount, final Currency currency, final LocalDate effectiveDate) throws InvoiceApiException {
// First, retrieve the invoice item in question
final InvoiceItemSqlDao invoiceItemSqlDao = transactional.become(InvoiceItemSqlDao.class);
final InvoiceItem invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString());
@@ -832,7 +863,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
if (invoice.getBalance().compareTo(BigDecimal.ZERO) < 0) {
final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
- invoice.getBalance().negate(), invoice.getCurrency());
+ invoice.getBalance().negate(), invoice.getCurrency());
transInvoiceItemDao.create(cbaAdjItem, context);
}
@@ -902,8 +933,8 @@ public class AuditedInvoiceDao implements InvoiceDao {
if (item.getInvoiceItemType() == InvoiceItemType.RECURRING) {
final RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
if ((recurringInvoiceItem.getEndDate() != null) &&
- (recurringInvoiceItem.getAmount() == null ||
- recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ (recurringInvoiceItem.getAmount() == null ||
+ recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
subscriptionForNextNotification = recurringInvoiceItem.getSubscriptionId();
shouldBeNotified = true;
break;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
index 54fce43..2d3b95b 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
@@ -570,6 +570,77 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
}
@Test(groups = "slow")
+ private void testFullRefundWithRepairAndInvoiceItemAdjustment() throws InvoiceApiException {
+ final BigDecimal refundAmount = new BigDecimal("20.00");
+ testRefundWithRepairAndInvoiceItemAdjustmentInternal(refundAmount);
+ }
+
+ @Test(groups = "slow")
+ private void testPartialRefundWithRepairAndInvoiceItemAdjustment() throws InvoiceApiException {
+ final BigDecimal refundAmount = new BigDecimal("7.00");
+ testRefundWithRepairAndInvoiceItemAdjustmentInternal(refundAmount);
+ }
+
+ private void testRefundWithRepairAndInvoiceItemAdjustmentInternal(final BigDecimal refundAmount) throws InvoiceApiException {
+
+ final UUID accountId = UUID.randomUUID();
+ final UUID bundleId = UUID.randomUUID();
+ final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
+
+ final Invoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
+ invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
+
+ final LocalDate startDate = new LocalDate(2011, 3, 1);
+ final LocalDate endDate = startDate.plusMonths(1);
+
+ final BigDecimal amount = new BigDecimal("20.0");
+ ;
+
+ // Recurring item
+ final RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test phase B", startDate,
+ endDate, amount, amount, Currency.USD);
+ invoiceItemSqlDao.create(item2, context);
+ BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(new BigDecimal("20.00")), 0);
+
+ // Pay the whole thing
+ final UUID paymentId = UUID.randomUUID();
+ final BigDecimal payment1 = amount;
+ final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, paymentId, invoice.getId(), new DateTime(), payment1, Currency.USD);
+ invoicePaymentDao.create(payment, context);
+ balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
+
+
+ // Repair the item (And add CBA item that should be generated)
+ final InvoiceItem repairItem = new RepairAdjInvoiceItem(invoice.getId(), accountId, startDate, endDate, amount.negate(), Currency.USD, item2.getId());
+ invoiceItemSqlDao.create(repairItem, context);
+
+ final InvoiceItem cbaItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), accountId, startDate, amount, Currency.USD);
+ invoiceItemSqlDao.create(cbaItem, context);
+
+ Map<UUID, BigDecimal> itemAdjustment = new HashMap<UUID, BigDecimal>();
+ itemAdjustment.put(item2.getId(), refundAmount);
+
+ invoiceDao.createRefund(paymentId, refundAmount, true, itemAdjustment, UUID.randomUUID(), context);
+ balance = invoiceDao.getAccountBalance(accountId);
+
+ final boolean partialRefund = refundAmount.compareTo(amount) < 0;
+ final BigDecimal cba = invoiceDao.getAccountCBA(accountId);
+ final Invoice savedInvoice = invoiceDao.getById(invoice.getId());
+ assertEquals(cba.compareTo(new BigDecimal("20.0")), 0);
+ if (partialRefund) {
+ // IB = 20 (rec) - 20 (repair) + 20 (cba) - (20 -7) = 7; AB = IB - CBA = 7 - 20 = -13
+ assertEquals(balance.compareTo(new BigDecimal("-13.0")), 0);
+ assertEquals(savedInvoice.getInvoiceItems().size(), 3);
+ } else {
+ assertEquals(balance.compareTo(new BigDecimal("0.0")), 0);
+ assertEquals(savedInvoice.getInvoiceItems().size(), 3);
+ }
+ }
+
+
+ @Test(groups = "slow")
public void testAccountBalanceWithSmallRefundAndCBANoAdj() throws InvoiceApiException {
BigDecimal refundAmount = new BigDecimal("7.00");
BigDecimal expectedBalance = new BigDecimal("-3.00");