killbill-uncached
Changes
beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 160(+160 -0)
Details
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index 2332a52..153a605 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -26,9 +26,11 @@ import javax.inject.Inject;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
+import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
+import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
@@ -39,7 +41,9 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.DefaultEntitlement;
+import com.ning.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.generator.DefaultInvoiceGeneratorWithSwitchRepairLogic;
import com.ning.billing.invoice.generator.DefaultInvoiceGeneratorWithSwitchRepairLogic.REPAIR_INVOICE_LOGIC;
@@ -700,4 +704,160 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 11), new LocalDate(2012, 6, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-249.95")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
}
+
+ @Test(groups = "slow")
+ public void testRepairWithFullItemAdjustment() throws Exception {
+
+ invoiceGenerator.setDefaultRepairLogic(REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR);
+
+ final LocalDate today = new LocalDate(2013, 7, 19);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ // 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 String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.ANNUAL;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+ assertNotNull(bpEntitlement);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+ assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+ // Move out of trials for interesting invoices adjustments
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), 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);
+
+
+ // Move clock to 2013-09-17
+ clock.addDays(30);
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE_ADJUSTMENT);
+ bpEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, callContext);
+ assertListenerStatus();
+
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+ assertEquals(invoices.size(), 2);
+ toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2014, 8, 18), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2202.67")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2013, 9, 17), InvoiceItemType.CBA_ADJ, new BigDecimal("2202.67")));
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+
+
+ //
+ // ITEM ADJUSTMENT PRIOR TO DOING THE REPAIR
+ //
+ final Invoice invoice1 = invoices.get(1);
+ final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
+ final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), PaymentStatus.SUCCESS, invoice1.getId(), Currency.USD);
+ final Payment payment1 = payments.get(0);
+
+ final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+ iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("197.28"));
+ busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
+ paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), iias, callContext);
+ assertListenerStatus();
+
+ try {
+ invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(clock.getUTCToday()), false, callContext);
+ Assert.fail("Should not gnenerated an new invoice");
+ } catch (InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+ }
+ }
+
+ //
+ // This is the exact same test as testRepairWithFullItemAdjustment except we now only do a partial item adjustment.
+ // The invoice code will NOT reinvoice for the remaining part as the item was both repaired and adjusted and so
+ // it does not have enough info to understand what should be re-invoiced.
+ //
+ // Note that there is no real use case for those scenarii in real life
+ //
+ @Test(groups = "slow")
+ public void testRepairWithPartialItemAdjustment() throws Exception {
+
+ invoiceGenerator.setDefaultRepairLogic(REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR);
+
+ final LocalDate today = new LocalDate(2013, 7, 19);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+ // 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 String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.ANNUAL;
+ final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+ assertNotNull(bpEntitlement);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+ assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+
+ // Move out of trials for interesting invoices adjustments
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), 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);
+
+
+ // Move clock to 2013-09-17
+ clock.addDays(30);
+ busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE_ADJUSTMENT);
+ bpEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, callContext);
+ assertListenerStatus();
+
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+ assertEquals(invoices.size(), 2);
+ toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+ new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2014, 8, 18), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2202.67")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2013, 9, 17), new LocalDate(2013, 9, 17), InvoiceItemType.CBA_ADJ, new BigDecimal("2202.67")));
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+
+
+ //
+ // ITEM ADJUSTMENT PRIOR TO DOING THE REPAIR
+ //
+ final Invoice invoice1 = invoices.get(1);
+ final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
+ final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), PaymentStatus.SUCCESS, invoice1.getId(), Currency.USD);
+ final Payment payment1 = payments.get(0);
+
+ final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+ iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("100.00"));
+ busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
+ paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), iias, callContext);
+ assertListenerStatus();
+
+ try {
+ invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(clock.getUTCToday()), false, callContext);
+ Assert.fail("Should not gnenerated an new invoice");
+ } catch (InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+ }
+ }
+
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 4d82d6d..78d5e9a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -64,7 +64,7 @@ import com.google.inject.Inject;
* Terminology for repair scenarii:
* <p/>
* - A 'repaired' item is an item that was generated and that needs to be repaired because the plan changed for that subscription on that period of time
- * - The 'repair' item is the item that cancels the (to be) repaired item; the repair item amount might not match (to be) repaired item because:
+ * - The 'repair' item is the item that cancels the (to be) repaired item; the repair item amount might not match the (to be) repaired item because:
* * the (to be) repaired item was already adjusted so we will only repair what is left
* * in case of partial repair we only repair the part that is not used
* - The 'reparee' item is only present on disk-- in the existing item list -- in case of full repair; in that case it represents the portion of the item that should still
@@ -181,7 +181,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final BigDecimal amountNegated = existingItem.getAmount() == null ? null : existingItem.getAmount().subtract(existingAdjustedPositiveAmount).negate();
if (amountNegated != null && amountNegated.compareTo(BigDecimal.ZERO) < 0) {
final RepairAdjInvoiceItem candidateRepairItem = new RepairAdjInvoiceItem(existingItem.getInvoiceId(), existingItem.getAccountId(), existingItem.getStartDate(), existingItem.getEndDate(), amountNegated, existingItem.getCurrency(), existingItem.getId());
- addRepairItem(existingItem, candidateRepairItem, proposedItems);
+ addRepairsForItem(existingItem, candidateRepairItem, proposedItems);
}
}
}
@@ -194,7 +194,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
* @param candidateRepairItem the repair item we would have if we were to repair the full period
* @param proposedItems the list of proposed items
*/
- void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
+ void addRepairsForItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
int nbTotalRepaireeDays = 0;
@@ -224,7 +224,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
}
});
- //Build the reparees
+ //Build the repaired
BigDecimal totalRepairItemAmount = BigDecimal.ZERO;
List<InvoiceItem> repairedItems = new ArrayList<InvoiceItem>();
InvoiceItem prevReparee = null;
@@ -391,10 +391,10 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
itemsToRemove.add(repairItem.getLinkedItemId());
final InvoiceItem repairedItem = getRepairedInvoiceItem(repairItem.getLinkedItemId(), existingItems);
- // if this is a full repair there is no reparee so nothing to remove; if not reparees need to be removed from proposed list
- if (!isFullRepair(repairedItem, repairItem, existingItems)) {
- removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
- }
+ // Always look for reparees; if this is a full repair there may not be any reparee to remove, but
+ // if this is a partial repair with an additional invoice item adjustment, this is seen as a full repair
+ // and yet there is a reparee to remove
+ removeProposedRepareesForPartialrepair(repairedItem, repairItem, proposedItems);
}
}
final Iterator<InvoiceItem> iterator = existingItems.iterator();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java b/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
index 34c48c7..4c43c7c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java
@@ -48,9 +48,9 @@ public class DefaultInvoiceGeneratorWithSwitchRepairLogic extends DefaultInvoice
}
@Override
- void addRepairItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
+ void addRepairsForItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
if (repairtLogic == REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR) {
- super.addRepairItem(repairedItem, candidateRepairItem, proposedItems);
+ super.addRepairsForItem(repairedItem, candidateRepairItem, proposedItems);
} else {
proposedItems.add(candidateRepairItem);
return;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
index c363267..569812f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/generator/TestDefaultInvoiceGeneratorRepairUnit.java
@@ -54,7 +54,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
this.defaultInvoiceGenerator = (DefaultInvoiceGenerator) generator;
}
- /********************************************* addRepairItem logic ********************************/
+ /********************************************* addRepairsForItem logic ********************************/
// repairedItem
// |-----------------------------------------------|
@@ -82,7 +82,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startProposed2, endDate, BigDecimal.TEN, rate, currency);
proposed.add(proposed2);
- defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+ defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
assertEquals(proposed.size(), 1);
assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
@@ -115,7 +115,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDateProposed1, BigDecimal.TEN, rate, currency);
proposed.add(proposed1);
- defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+ defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
assertEquals(proposed.size(), 1);
assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
@@ -149,7 +149,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed1, endDate, BigDecimal.TEN, rate, currency);
proposed.add(proposed1);
- defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+ defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
assertEquals(proposed.size(), 1);
assertEquals(proposed.get(0).getStartDate(), startDate);
@@ -189,7 +189,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDateProposed3, endDate, BigDecimal.TEN, rate, currency);
proposed.add(proposed3);
- defaultInvoiceGenerator.addRepairItem(repairedItem, candidateRepairItem, proposed);
+ defaultInvoiceGenerator.addRepairsForItem(repairedItem, candidateRepairItem, proposed);
assertEquals(proposed.size(), 2);
assertEquals(proposed.get(0).getStartDate(), endDateProposed1);
@@ -206,7 +206,7 @@ public class TestDefaultInvoiceGeneratorRepairUnit extends InvoiceTestSuiteNoDB
}
- /********************************************* addRepairItems logic ********************************/
+ /********************************************* addRepairsForItems logic ********************************/
@Test(groups = "fast")
public void testAddRepairedItemsItemsRecurringPrice() {