killbill-aplcache
Changes
beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java 38(+19 -19)
beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 39(+10 -29)
invoice/src/test/java/com/ning/billing/invoice/generator/DefaultInvoiceGeneratorWithSwitchRepairLogic.java 65(+0 -65)
Details
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
index c52cdbd..3dca515 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -42,7 +42,7 @@ import com.ning.billing.currency.glue.CurrencyModule;
import com.ning.billing.entitlement.EntitlementService;
import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
import com.ning.billing.invoice.api.InvoiceService;
-import com.ning.billing.invoice.generator.DefaultInvoiceGeneratorWithSwitchRepairLogic;
+import com.ning.billing.invoice.generator.DefaultInvoiceGenerator;
import com.ning.billing.invoice.generator.InvoiceGenerator;
import com.ning.billing.invoice.glue.DefaultInvoiceModule;
import com.ning.billing.junction.glue.DefaultJunctionModule;
@@ -144,8 +144,7 @@ public class BeatrixIntegrationModule extends AbstractModule {
}
protected void installInvoiceGenerator() {
- bind(InvoiceGenerator.class).to(DefaultInvoiceGeneratorWithSwitchRepairLogic.class).asEagerSingleton();
- bind(DefaultInvoiceGeneratorWithSwitchRepairLogic.class).asEagerSingleton();
+ bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
}
}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 6a9d48c..c5cd4b0 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -181,8 +181,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-102.13")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("102.13")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.83")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.83")));
// Add 10 days to generate next invoice. We verify that we indeed have a notification for nextBillingDate
addDaysAndCheckForCompletion(10, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -190,7 +190,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
// Item for the upgraded recurring plan
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-102.13")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 2), new LocalDate(2012, 8, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-104.83")));
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 8, 31), callContext);
@@ -276,14 +276,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
// New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.64")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("166.64")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
// New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-166.64")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("-169.32")));
// Move one month ahead, and check if we get the next invoice
addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -378,13 +378,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
// New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-166.64")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("166.64")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
// New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("241.88")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-166.64")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 8, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-169.32")));
// Move one month ahead, and check if we get the next invoice
addDaysAndCheckForCompletion(30, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -465,16 +465,16 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-102.13")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("102.13")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.83")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.83")));
// Do an upgrade now
checkChangePlanWithOverdueState(baseEntitlement, false, false);
invoiceChecker.checkRepairedInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-102.13")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("102.13")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 23), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-104.83")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("104.83")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-64.51")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("64.51")));
@@ -487,7 +487,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
// Verify the account balance:
- assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-11.79")), 0);
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-14.49")), 0);
}
@Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan and use of credit", enabled=false)
@@ -643,8 +643,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkInvoice(account.getId(), 3, callContext,
// Item for the upgraded recurring plan
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-76.60")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("76.60")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
@@ -654,8 +654,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkRepairedInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-76.60")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("76.60")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-48.37")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("48.37")));
@@ -665,7 +665,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
- assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-8.88")), 0);
+ assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-12.91")), 0);
}
@Test(groups = "slow", description = "Test overdue from non paid external charge")
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
index 6a15797..77fa87a 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
@@ -208,8 +208,8 @@ public class TestBundleTransfer extends TestIntegrationBase {
// CHECK OLD ACCOUNTS ITEMS
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 9), InvoiceItemType.RECURRING, new BigDecimal("66.66")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), new LocalDate(2012, 5, 9), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-49.99")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), new LocalDate(2012, 5, 3), InvoiceItemType.CBA_ADJ, new BigDecimal("49.99")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), new LocalDate(2012, 5, 9), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-50.00")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), new LocalDate(2012, 5, 3), InvoiceItemType.CBA_ADJ, new BigDecimal("50.00")));
invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
// CHECK NEW ACCOUNT ITEMS
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 31f8857..c3b6a6f 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
@@ -22,8 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
-import javax.inject.Inject;
-
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.testng.Assert;
@@ -45,8 +43,6 @@ 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;
import com.ning.billing.payment.api.Payment;
import com.ning.billing.payment.api.PaymentStatus;
@@ -57,14 +53,9 @@ import static org.testng.Assert.assertNotNull;
public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
- @Inject
- private DefaultInvoiceGeneratorWithSwitchRepairLogic invoiceGenerator;
-
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
super.afterMethod();
- // Make sure to reset to default invoice logic so subsequent test will pass
- invoiceGenerator.setDefaultRepairLogic(REPAIR_INVOICE_LOGIC.PARTIAL_REPAIR);
}
@Test(groups = "slow")
@@ -138,7 +129,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
// Force a plan change
//
changeEntitlementAndCheckForCompletion(bpEntitlement, "Blowdart", term, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT);
-
invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
assertEquals(invoices.size(), 3);
@@ -151,9 +141,10 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 5, 2), InvoiceItemType.ITEM_ADJ, new BigDecimal("-10")),
// TODO PIERRE The cba start_date/end_date are created using the callcontext
new ExpectedInvoiceItemCheck(callContext.getCreatedDate().toLocalDate(), callContext.getCreatedDate().toLocalDate(), InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
- // You can check here that 239.95 - 249.95/31 = 231.88
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-231.88")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 5, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("231.88")));
+ // The pro-rated piece is ~ 249.95 - (249.95 / 31) ~ 241.88. However we adjusted the item so max available amount is 249.95 - 10 = 239.95.
+ // So we consume all of it since max amount is less than 241.88.
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-239.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 5, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("239.95")));
invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -286,8 +277,8 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 7), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("8.02")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 7), new LocalDate(2012, 5, 7), InvoiceItemType.CBA_ADJ, new BigDecimal("-8.02")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-7.69")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 5, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("7.69")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-7.70")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 5, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("7.70")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -323,8 +314,8 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 7), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("8.02")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 7), new LocalDate(2012, 5, 7), InvoiceItemType.CBA_ADJ, new BigDecimal("-8.02")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-7.69")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 5, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("7.69")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-7.70")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 5, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("7.70")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -365,8 +356,8 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 7), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("8.02")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 7), new LocalDate(2012, 5, 7), InvoiceItemType.CBA_ADJ, new BigDecimal("-8.02")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-7.69")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 5, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("7.69")));
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-7.70")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 8), new LocalDate(2012, 5, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("7.70")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -496,8 +487,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
@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));
@@ -528,7 +517,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
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);
@@ -543,8 +531,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
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
//
@@ -577,8 +563,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
@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));
@@ -609,7 +593,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
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);
@@ -624,8 +607,6 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
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
//
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
index c0ab36a..e80d59e 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
@@ -20,6 +20,8 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -39,7 +41,9 @@ import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.subscription.api.SubscriptionBase;
import com.ning.billing.util.callcontext.CallContext;
+import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import static org.testng.Assert.assertNotNull;
@@ -92,7 +96,34 @@ public class InvoiceChecker {
final List<InvoiceItem> actual = invoice.getInvoiceItems();
Assert.assertEquals(actual.size(), expected.size());
for (final ExpectedInvoiceItemCheck cur : expected) {
+
boolean found = false;
+
+ // First try to find exact match; this is necessary because the for loop below might encounter a similar item -- for instance
+ // same type, same dates, but different amount and choke.
+ final InvoiceItem foundItem = Iterables.tryFind(actual, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ if (input.getInvoiceItemType() != cur.getType() || (cur.shouldCheckDates() && input.getStartDate().compareTo(cur.getStartDate()) != 0)) {
+ return false;
+ }
+ if (input.getAmount().compareTo(cur.getAmount()) != 0) {
+ return false;
+ }
+
+ if (!cur.shouldCheckDates() ||
+ (cur.getEndDate() == null && input.getEndDate() == null) ||
+ (cur.getEndDate() != null && input.getEndDate() != null && cur.getEndDate().compareTo(input.getEndDate()) == 0)) {
+ return true;
+ }
+ return false;
+ }
+ }).orNull();
+ if (foundItem != null) {
+ continue;
+ }
+
+ // If we could not find it, we still loop again, so that error message helps to debug when there is a 'similar' item.
for (final InvoiceItem in : actual) {
// Match first on type and start date
if (in.getInvoiceItemType() != cur.getType() || (cur.shouldCheckDates() && in.getStartDate().compareTo(cur.getStartDate()) != 0)) {
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 b5af327..ca202d8 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
@@ -18,9 +18,6 @@ package com.ning.billing.invoice.generator;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -28,7 +25,6 @@ import java.util.UUID;
import javax.annotation.Nullable;
-import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.Months;
import org.slf4j.Logger;
@@ -41,7 +37,6 @@ import com.ning.billing.clock.Clock;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.model.BillingMode;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
@@ -50,47 +45,14 @@ import com.ning.billing.invoice.model.InvalidDateSequenceException;
import com.ning.billing.invoice.model.InvoicingConfiguration;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItemData;
-import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
import com.ning.billing.invoice.tree.AccountItemTree;
import com.ning.billing.junction.BillingEvent;
import com.ning.billing.junction.BillingEventSet;
import com.ning.billing.junction.BillingModeType;
import com.ning.billing.util.config.InvoiceConfig;
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
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 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
- * be invoiced for the plan of the repaired item. In case of partial repair it is merged with the repair item and does not exist except as a virtual item in the proposed list
- * <p/>
- * <p/>
- * <p/>
- * Example. We had a 20 subscription for a given period; we charged that amount and later discovered that only 3/4 of the time period were used after which the subscription was cancelled (immediate canellation)
- * <p/>
- * Full repair logic:
- * <p/>
- * Invoice 1: Invoice 2:
- * +20 (repaired) +5 (reparee)
- * -20 (repair)
- * <p/>
- * Partial repair logic:
- * <p/>
- * Invoice 1: Invoice 2: (N/A)
- * +20 (repaired)
- * -15 (repair)
- * <p/>
- * The current version of the code uses partial repair logic but is able to deal with 'full repair' scenarii.
- */
-
public class DefaultInvoiceGenerator implements InvoiceGenerator {
private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
@@ -122,7 +84,6 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final AccountItemTree tree = new AccountItemTree(accountId);
- final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
if (existingInvoices != null) {
for (final Invoice invoice : existingInvoices) {
@@ -134,22 +95,11 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charges etc.
!events.getSubscriptionIdsWithAutoInvoiceOff()
.contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
-
- if ( item.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ) {
- existingItems.add(item);
- } else {
- tree.addItem(item, allSeenItems);
- }
-
+ tree.addItem(item, allSeenItems);
}
}
}
}
- tree.build();
- final List<InvoiceItem> recurringExistingItems = tree.getCurrentExistingItemsView();
- if (recurringExistingItems.size() > 0) {
- existingItems.addAll(recurringExistingItems);
- }
final LocalDate adjustedTargetDate = adjustTargetDate(existingInvoices, targetDate);
@@ -159,173 +109,11 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
// Generate list of proposed invoice items based on billing events from junction-- proposed items are ALL items since beginning of time
final List<InvoiceItem> proposedItems = generateInvoiceItems(invoiceId, accountId, events, adjustedTargetDate, targetCurrency);
- // Remove from both lists the items in common
- removeMatchingInvoiceItems(existingItems, proposedItems);
-
- // We don't want the Fixed items to be repaired -- as they are setup fees that should be paid
- removeRemainingFixedItemsFromExisting(existingItems);
-
-
- // Add repair items based on what is left in existing items
- addRepairItems(existingItems, proposedItems);
-
- // Finally add this new items on the new invoice
- invoice.addInvoiceItems(proposedItems);
-
- return proposedItems.size() != 0 ? invoice : null;
- }
-
- private void removeRemainingFixedItemsFromExisting(final List<InvoiceItem> existingItems) {
- final Iterator<InvoiceItem> it = existingItems.iterator();
- while (it.hasNext()) {
- final InvoiceItem cur = it.next();
- if (cur.getInvoiceItemType() == InvoiceItemType.FIXED) {
- it.remove();
- }
- }
- }
-
- /**
- * At this point either we have 0 existingItem left or those left need to be repaired
- *
- * @param existingItems the list of remaining existing items
- * @param proposedItems the list of remaining proposed items
- */
- void addRepairItems(final List<InvoiceItem> existingItems, final List<InvoiceItem> proposedItems) {
- for (final InvoiceItem existingItem : existingItems) {
- if (existingItem.getInvoiceItemType() == InvoiceItemType.RECURRING) {
- final BigDecimal existingAdjustedPositiveAmount = getAdjustedPositiveAmount(existingItems, existingItem.getId());
- 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());
- addRepairsForItem(existingItem, candidateRepairItem, proposedItems);
- }
- }
- }
- }
-
- /**
- * Add the repair item for the (yet to be) repairedItem. It will merge the candidateRepairItem with reparee item
- *
- * @param repairedItem the item being repaired
- * @param candidateRepairItem the repair item we would have if we were to repair the full period
- * @param proposedItems the list of proposed items
- */
- void addRepairsForItem(final InvoiceItem repairedItem, final RepairAdjInvoiceItem candidateRepairItem, final List<InvoiceItem> proposedItems) {
-
- int nbTotalRepaireeDays = 0;
-
- // totalRepareeItemAmount is negative and represents the portion left after we removed the adjustments for the total period for all the reparees combined
- BigDecimal totalRepareeItemAmount = candidateRepairItem.getAmount();
- final List<InvoiceItem> reparees = new ArrayList<InvoiceItem>();
- for (final InvoiceItem cur : proposedItems) {
- if (isRepareeItemForRepairedItem(repairedItem, cur)) {
- nbTotalRepaireeDays += Days.daysBetween(cur.getStartDate(), cur.getEndDate()).getDays();
- reparees.add(cur);
- totalRepareeItemAmount = totalRepareeItemAmount.add(cur.getAmount());
- }
- }
- int nbTotalRepairedDays = Days.daysBetween(candidateRepairItem.getStartDate(), candidateRepairItem.getEndDate()).getDays() - nbTotalRepaireeDays;
-
- // If we repaired the full period there is no repairee item
- if (reparees.size() == 0) {
- proposedItems.add(candidateRepairItem);
- return;
- }
+ tree.mergeWithProposedItems(proposedItems);
+ final List<InvoiceItem> finalItems = tree.getCurrentExistingItemsView();
+ invoice.addInvoiceItems(finalItems);
- // Sort the reparees based on startDate in order to create the repair items -- based on the endDate (previous repairee) -> startDate (next reparee)
- Collections.sort(reparees, new Comparator<InvoiceItem>() {
- @Override
- public int compare(final InvoiceItem o1, final InvoiceItem o2) {
- return o1.getStartDate().compareTo(o2.getStartDate());
- }
- });
-
- //Build the repaired
- BigDecimal totalRepairItemAmount = BigDecimal.ZERO;
- List<InvoiceItem> repairedItems = new ArrayList<InvoiceItem>();
- InvoiceItem prevReparee = null;
- final Iterator<InvoiceItem> it = reparees.iterator();
- while (it.hasNext()) {
- final InvoiceItem nextReparee = it.next();
- if (prevReparee != null || nextReparee.getStartDate().compareTo(repairedItem.getStartDate()) > 0) {
- final LocalDate repairedStartDate = (prevReparee == null) ? repairedItem.getStartDate() : prevReparee.getEndDate();
- final LocalDate repairedEndDateDate = nextReparee.getStartDate();
- // repairItemAmount is an approximation of the exact amount by simply prorating totalRepareeItemAmount in the repair period; we make sure last item is calculated based
- // on what is left so the sum of all repairs amount is exactly correct
- final BigDecimal repairItemAmount = (nextReparee.getEndDate().compareTo(candidateRepairItem.getEndDate()) != 0) ?
- InvoiceDateUtils.calculateProrationBetweenDates(repairedStartDate, repairedEndDateDate, nbTotalRepairedDays).multiply(totalRepareeItemAmount) :
- totalRepareeItemAmount.subtract(totalRepairItemAmount);
- totalRepairItemAmount = totalRepairItemAmount.add(repairItemAmount);
- final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(candidateRepairItem.getInvoiceId(), candidateRepairItem.getAccountId(), repairedStartDate, repairedEndDateDate, repairItemAmount, candidateRepairItem.getCurrency(), repairedItem.getId());
- repairedItems.add(repairItem);
- }
- prevReparee = nextReparee;
- }
-
- // In case we end up with a repair up to the service endDate we need to add this extra item-- this is the 'classic' case with one repairee/repair item
- if (prevReparee.getEndDate().compareTo(candidateRepairItem.getEndDate()) != 0) {
- final BigDecimal repairItemAmount = totalRepareeItemAmount.subtract(totalRepairItemAmount);
- final RepairAdjInvoiceItem repairItem = new RepairAdjInvoiceItem(candidateRepairItem.getInvoiceId(), candidateRepairItem.getAccountId(), prevReparee.getEndDate(), candidateRepairItem.getEndDate(), repairItemAmount, candidateRepairItem.getCurrency(), repairedItem.getId());
- repairedItems.add(repairItem);
- }
-
- // Finally remove all reparees from the proposed items and add all repaired items in the invoice
- for (InvoiceItem reparee : reparees) {
- proposedItems.remove(reparee);
- }
- proposedItems.addAll(repairedItems);
- }
-
- /**
- * Check whether or not the invoiceItem passed is the reparee for that repaired invoice item
- *
- * @param repairedInvoiceItem the repaired invoice item
- * @param invoiceItem any invoice item to compare to
- * @return true if invoiceItem is the reparee for that repaired invoice item
- */
- boolean isRepareeItemForRepairedItem(final InvoiceItem repairedInvoiceItem, final InvoiceItem invoiceItem) {
- return !repairedInvoiceItem.getId().equals(invoiceItem.getId()) &&
- repairedInvoiceItem.getInvoiceItemType().equals(invoiceItem.getInvoiceItemType()) &&
- // We assume the items are correctly created, so that the subscription id check implicitly
- // verifies that account id and bundle id matches
- repairedInvoiceItem.getSubscriptionId().equals(invoiceItem.getSubscriptionId()) &&
- // service period for reparee should be included in service period of repaired-- true for startDate and endDate
- repairedInvoiceItem.getStartDate().compareTo(invoiceItem.getStartDate()) <= 0 &&
- // Similarly, check the "portion used" is less than the original service end date. The check
- // is strict, otherwise there wouldn't be anything to repair
- ((repairedInvoiceItem.getEndDate() == null && invoiceItem.getEndDate() == null) ||
- (repairedInvoiceItem.getEndDate() != null && invoiceItem.getEndDate() != null &&
- repairedInvoiceItem.getEndDate().compareTo(invoiceItem.getEndDate()) >= 0)) &&
- // Finally, for the tricky part... In case of complete repairs, the new item will always meet all of the
- // following conditions: same type, subscription, start date. Depending on the catalog configuration, the end
- // date check could also match (e.g. repair from annual to monthly). For that scenario, we need to default
- // to catalog checks (the rate check is a lame check for versioned catalogs).
- Objects.firstNonNull(repairedInvoiceItem.getPlanName(), "").equals(Objects.firstNonNull(invoiceItem.getPlanName(), "")) &&
- Objects.firstNonNull(repairedInvoiceItem.getPhaseName(), "").equals(Objects.firstNonNull(invoiceItem.getPhaseName(), "")) &&
- Objects.firstNonNull(repairedInvoiceItem.getRate(), BigDecimal.ZERO).compareTo(Objects.firstNonNull(invoiceItem.getRate(), BigDecimal.ZERO)) == 0;
- }
-
- // We check to see if there are any adjustments that point to the item we are trying to repair
- // If we did any CREDIT_ADJ or REFUND_ADJ, then we unfortunately we can't know what is the intent
- // was as it applies to the full Invoice, so we ignore it. That might result in an extra positive CBA
- // that would have to be corrected manually. This is the best we can do, and administrators should always
- // use ITEM_ADJUSTMENT rather than CREDIT_ADJ or REFUND_ADJ when possible.
- //
- BigDecimal getAdjustedPositiveAmount(final List<InvoiceItem> existingItems, final UUID linkedItemId) {
- BigDecimal totalAdjustedOnItem = BigDecimal.ZERO;
- final Collection<InvoiceItem> invoiceItems = Collections2.filter(existingItems, new Predicate<InvoiceItem>() {
- @Override
- public boolean apply(final InvoiceItem item) {
- return item.getInvoiceItemType() == InvoiceItemType.ITEM_ADJ &&
- item.getLinkedItemId() != null && item.getLinkedItemId().equals(linkedItemId);
- }
- });
-
- for (final InvoiceItem invoiceItem : invoiceItems) {
- totalAdjustedOnItem = totalAdjustedOnItem.add(invoiceItem.getAmount());
- }
- return totalAdjustedOnItem.negate();
+ return finalItems.size() != 0 ? invoice : null;
}
private void validateTargetDate(final LocalDate targetDate) throws InvoiceApiException {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
index b219108..01e4cc6 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
@@ -60,6 +60,7 @@ public class AccountItemTree {
}
}
+
public void mergeWithProposedItems(final List<InvoiceItem> proposedItems) {
for (SubscriptionItemTree tree : subscriptionItemTree.values()) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java b/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java
index 4d2801d..eec3032 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/Item.java
@@ -76,7 +76,7 @@ public class Item {
this.amount = item.amount;
this.rate = item.rate;
this.currency = item.currency;
- this.linkedId = item.linkedId;
+ this.linkedId = this.id; // STEPH called from flatten where linkedId does not exist
this.createdDate = item.createdDate;
this.currentRepairedAmount = item.currentRepairedAmount;
this.adjustedAmount = item.adjustedAmount;
@@ -116,7 +116,7 @@ public class Item {
final BigDecimal positiveAmount = prorated ?
InvoiceDateUtils.calculateProrationBetweenDates(newStartDate, newEndDate, nbTotalDays)
- .multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE) :
+ .multiply(amount).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE) :
amount;
if (action == ItemAction.ADD) {
@@ -125,7 +125,7 @@ public class Item {
final BigDecimal maxAvailableAmountAfterAdj = amount.subtract(adjustedAmount);
final BigDecimal maxAvailableAmountForRepair = maxAvailableAmountAfterAdj.subtract(currentRepairedAmount);
final BigDecimal positiveAmountForRepair = positiveAmount.compareTo(maxAvailableAmountForRepair) <= 0 ? positiveAmount : maxAvailableAmountForRepair;
- return new RepairAdjInvoiceItem(invoiceId, accountId, newStartDate, newEndDate, positiveAmountForRepair.negate(), currency, linkedId);
+ return positiveAmountForRepair.compareTo(BigDecimal.ZERO) > 0 ? new RepairAdjInvoiceItem(invoiceId, accountId, newStartDate, newEndDate, positiveAmountForRepair.negate(), currency, linkedId) : null;
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
index dc258f5..18be2ab 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsInterval.java
@@ -84,7 +84,7 @@ public class ItemsInterval {
}
}
- public void buildFromItems(final List<Item> output, final boolean addRepair) {
+ public void buildFromItems(final List<Item> output, final boolean isParentRepair, final boolean mergeMode) {
final Set<UUID> repairedIds = new HashSet<UUID>();
final ListIterator<Item> it = items.listIterator(items.size());
@@ -92,7 +92,11 @@ public class ItemsInterval {
final Item cur = it.previous();
switch (cur.getAction()) {
case ADD:
- if (!repairedIds.contains(cur.getId())) {
+ if (!mergeMode && !repairedIds.contains(cur.getId())) {
+ output.add(cur);
+ }
+
+ if (mergeMode && !isParentRepair) {
output.add(cur);
}
break;
@@ -100,7 +104,7 @@ public class ItemsInterval {
if (cur.getLinkedId() != null) {
repairedIds.add(cur.getLinkedId());
}
- if (addRepair) {
+ if (mergeMode) {
output.add(cur);
}
break;
@@ -132,19 +136,25 @@ public class ItemsInterval {
items.clear();
}
- private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean addRepair) {
+ // STEPH so complicated...
+ public boolean isRepairNode() {
+ return items.size() == 1 && items.get(0).getAction() == ItemAction.CANCEL;
+ }
+
+ private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean mergeMode) {
final List<Item> itemToConsider = new LinkedList<Item>();
- buildFromItems(itemToConsider, addRepair);
+ // STEPH flags...
+ buildFromItems(itemToConsider, true, mergeMode);
Iterator<Item> it = itemToConsider.iterator();
while (it.hasNext()) {
final Item cur = it.next();
- if (cur.getAction() == ItemAction.CANCEL && !addRepair) {
+ if (cur.getAction() == ItemAction.CANCEL && !mergeMode) {
continue;
}
final Item result = new Item(cur.toProratedInvoiceItem(startDate, endDate), cur.getAction());
- if (cur.getAction() == ItemAction.CANCEL) {
+ if (cur.getAction() == ItemAction.CANCEL && result != null) {
cur.incrementCurrentRepairedAmount(result.getAmount());
}
return result;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
index 1e0d65a..6ecdcf6 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
@@ -17,14 +17,11 @@
package com.ning.billing.invoice.tree;
import java.math.BigDecimal;
-import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
-import com.ning.billing.invoice.api.InvoiceItem;
-
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
@@ -51,11 +48,11 @@ public class NodeInterval {
this.rightSibling = null;
}
- public void build(final List<Item> output, boolean addRepair) {
+ public void build(final List<Item> output, boolean isParentRepair, boolean mergeMode) {
// There is no sub-interval, just add our own items.
if (leftChild == null) {
- items.buildFromItems(output, addRepair);
+ items.buildFromItems(output, isParentRepair, mergeMode);
return;
}
@@ -63,14 +60,14 @@ public class NodeInterval {
NodeInterval curChild = leftChild;
while (curChild != null) {
if (curChild.getStart().compareTo(curDate) > 0) {
- items.buildForMissingInterval(curDate, curChild.getStart(), output, addRepair);
+ items.buildForMissingInterval(curDate, curChild.getStart(), output, mergeMode);
}
- curChild.build(output, addRepair);
+ curChild.build(output, isRepairNode(), mergeMode);
curDate = curChild.getEnd();
curChild = curChild.getRightSibling();
}
if (curDate.compareTo(end) < 0) {
- items.buildForMissingInterval(curDate, end, output, addRepair);
+ items.buildForMissingInterval(curDate, end, output, mergeMode);
}
}
@@ -104,7 +101,7 @@ public class NodeInterval {
if (!isRoot()) {
throw new TreeNodeException("findNode can only be called from root");
}
- return findNodeRecursively(this, date, targetItemId);
+ return findNodeRecursively2(this, date, targetItemId);
}
private NodeInterval findNodeRecursively(final NodeInterval curNode, final LocalDate date, final UUID targetItemId) {
@@ -125,6 +122,24 @@ public class NodeInterval {
return null;
}
+ private NodeInterval findNodeRecursively2(final NodeInterval curNode, final LocalDate date, final UUID targetItemId) {
+
+ if (!curNode.isRoot() && curNode.containsItem(targetItemId)) {
+ return curNode;
+ }
+
+ NodeInterval curChild = curNode.getLeftChild();
+ while (curChild != null) {
+ final NodeInterval result = findNodeRecursively2(curChild, date, targetItemId);
+ if (result != null) {
+ return result;
+ }
+ curChild = curChild.getRightSibling();
+ }
+ return null;
+ }
+
+
public boolean mergeProposedItem(final NodeInterval newNode) {
Preconditions.checkState(newNode.getItems().size() == 1, "Expected new node to have only one item");
@@ -136,7 +151,6 @@ public class NodeInterval {
}
computeRootInterval(newNode);
-
if (leftChild == null) {
leftChild = newNode;
return true;
@@ -186,6 +200,10 @@ public class NodeInterval {
addNode(newNode);
}
+ public boolean isRepairNode() {
+ return items.isRepairNode();
+ }
+
// STEPH TODO are parents correctly maintained and/or do we need them?
private void addNode(final NodeInterval newNode) {
final Item item = newNode.getItems().get(0);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
index e137c4d..e59c373 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
@@ -16,6 +16,7 @@
package com.ning.billing.invoice.tree;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -61,7 +62,7 @@ public class SubscriptionItemTree {
root.addAdjustment(item.getStartDate(), item.getAmount(), item.getLinkedItemId());
}
pendingItemAdj.clear();
- root.build(items, false);
+ root.build(items, false, false);
isBuilt = true;
}
@@ -71,7 +72,6 @@ public class SubscriptionItemTree {
}
root = new NodeInterval();
for (Item item : items) {
- final InvoiceItem invoiceItem = item.toInvoiceItem();
Preconditions.checkState(item.getAction() == ItemAction.ADD);
root.addNodeInterval(new NodeInterval(root, new Item(item, reverse ? ItemAction.CANCEL : ItemAction.ADD)));
}
@@ -81,7 +81,7 @@ public class SubscriptionItemTree {
public void buildForMerge() {
Preconditions.checkState(!isBuilt);
- root.build(items, true);
+ root.build(items, false, true);
isBuilt = true;
}
@@ -145,11 +145,16 @@ public class SubscriptionItemTree {
// STEPH TODO check that nodeInterval don't overlap or throw. => double billing...
final List<InvoiceItem> result = new LinkedList<InvoiceItem>();
result.addAll(remainingFixedItems);
- result.addAll(Collections2.transform(items, new Function<Item, InvoiceItem>() {
+ result.addAll(Collections2.filter(Collections2.transform(items, new Function<Item, InvoiceItem>() {
@Override
public InvoiceItem apply(final Item input) {
return input.toInvoiceItem();
}
+ }), new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(@Nullable final InvoiceItem input) {
+ return input != null;
+ }
}));
return result;
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
index 6eddcc8..9692739 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -354,6 +354,26 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
}
+ @Test(groups = "fast")
+ public void testMergeWithNoExisting() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount = monthlyRate;
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount, monthlyRate, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ expectedResult.add(proposed1);
+ verifyResult(tree.getView(), expectedResult);
+ }
@Test(groups = "fast")
public void testMergeTwoSimilarItems() {
@@ -408,7 +428,7 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
}
@Test(groups = "fast")
- public void testMergeWithInitialRepair() {
+ public void testMergeCancellationWithInitialRepair() {
final LocalDate startDate = new LocalDate(2014, 1, 1);
final LocalDate blockDate = new LocalDate(2014, 1, 25);
@@ -431,13 +451,12 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
final List<InvoiceItem> expectedResult = Lists.newLinkedList();
final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, startDate, blockDate, new BigDecimal("-9.29"), currency, monthly1.getId());
expectedResult.add(repair);
- expectedResult.add(proposed1);
verifyResult(tree.getView(), expectedResult);
}
@Test(groups = "fast")
- public void testMergeWithFinalRepair() {
+ public void testMergeCancellationWithFinalRepair() {
final LocalDate startDate = new LocalDate(2014, 1, 1);
final LocalDate cancelDate = new LocalDate(2014, 1, 25);
@@ -453,19 +472,17 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.flatten(true);
final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, monthlyAmount1, monthlyRate1, currency);
-
tree.mergeProposedItem(proposed1);
tree.buildForMerge();
final List<InvoiceItem> expectedResult = Lists.newLinkedList();
final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, cancelDate, endDate, new BigDecimal("-2.71"), currency, monthly1.getId());
- expectedResult.add(proposed1);
expectedResult.add(repair);
verifyResult(tree.getView(), expectedResult);
}
@Test(groups = "fast")
- public void testMergeWithMiddleRepair() {
+ public void testMergeCancellationWithMiddleRepair() {
final LocalDate startDate = new LocalDate(2014, 1, 1);
final LocalDate blockDate = new LocalDate(2014, 1, 13);
@@ -490,14 +507,12 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
final List<InvoiceItem> expectedResult = Lists.newLinkedList();
final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate, unblockDate, new BigDecimal("-4.65"), currency, monthly1.getId());
- expectedResult.add(proposed1);
expectedResult.add(repair);
- expectedResult.add(proposed2);
verifyResult(tree.getView(), expectedResult);
}
@Test(groups = "fast")
- public void testMergeWithTwoMiddleRepair() {
+ public void testMergeCancellationWithTwoMiddleRepair() {
final LocalDate startDate = new LocalDate(2014, 1, 1);
final LocalDate blockDate1 = new LocalDate(2014, 1, 7);
@@ -526,14 +541,85 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
final List<InvoiceItem> expectedResult = Lists.newLinkedList();
final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate1, unblockDate1, new BigDecimal("-2.32"), currency, monthly.getId());
final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, blockDate2, unblockDate2, new BigDecimal("-3.10"), currency, monthly.getId());
- expectedResult.add(proposed1);
expectedResult.add(repair1);
- expectedResult.add(proposed2);
expectedResult.add(repair2);
- expectedResult.add(proposed3);
verifyResult(tree.getView(), expectedResult);
}
+ @Test(groups = "fast")
+ public void testMergeUpgradeWithFinalRepair() {
+
+ final LocalDate startDate = new LocalDate(2014, 1, 1);
+ final LocalDate upgradeDate = new LocalDate(2014, 1, 25);
+ final LocalDate endDate = new LocalDate(2014, 2, 1);
+
+ final BigDecimal monthlyRate1 = new BigDecimal("12.00");
+ final BigDecimal monthlyAmount1 = monthlyRate1;
+
+ final BigDecimal monthlyRate2 = new BigDecimal("20.00");
+ final BigDecimal monthlyAmount2 = monthlyRate1;
+
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem monthly1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, monthlyAmount1, monthlyRate1, currency);
+ tree.addItem(monthly1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, upgradeDate, monthlyAmount1, monthlyRate1, currency);
+ final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", upgradeDate, endDate, monthlyAmount2, monthlyRate2, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.mergeProposedItem(proposed2);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair = new RepairAdjInvoiceItem(invoiceId, accountId, upgradeDate, endDate, new BigDecimal("-2.71"), currency, monthly1.getId());
+ expectedResult.add(proposed2);
+ expectedResult.add(repair);
+ verifyResult(tree.getView(), expectedResult);
+ }
+
+ @Test(groups = "fast")
+ public void testMergeWithSecondRepair() {
+
+ final LocalDate startDate = new LocalDate(2012, 5, 1);
+ final LocalDate endDate = new LocalDate(2012, 6, 1);
+ final LocalDate change1 = new LocalDate(2012, 5, 7);
+ final LocalDate change2 = new LocalDate(2012, 5, 8);
+
+ final BigDecimal rate1 = new BigDecimal("599.95");
+ final BigDecimal amount1 = rate1;
+
+ final BigDecimal rate2 = new BigDecimal("9.95");
+ final BigDecimal proratedAmount2 = new BigDecimal("8.02");
+
+ final BigDecimal rate3 = new BigDecimal("29.95");
+ final BigDecimal proratedAmount3 = new BigDecimal("23.19");
+
+ final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+ final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", change1, endDate, proratedAmount2, rate2, currency);
+ final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, change1, endDate, new BigDecimal("-483.86"), currency, initial.getId());
+
+ tree.addItem(initial);
+ tree.addItem(newItem1);
+ tree.addItem(repair1);
+ tree.flatten(true);
+
+ final InvoiceItem proposed1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, change1, amount1, rate1, currency);
+ final InvoiceItem proposed2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "foo", "foo", change1, change2, proratedAmount3, rate2, currency);
+ final InvoiceItem proposed3 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "bar", "bar", change2, endDate, proratedAmount3, rate3, currency);
+ tree.mergeProposedItem(proposed1);
+ tree.mergeProposedItem(proposed2);
+ tree.mergeProposedItem(proposed3);
+ tree.buildForMerge();
+
+ final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+ final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, change2, endDate, new BigDecimal("-7.70"), currency, initial.getId());
+ expectedResult.add(proposed3);
+ expectedResult.add(repair2);
+ verifyResult(tree.getView(), expectedResult);
+
+ }
@Test(groups = "fast")
public void testWithExistingFixedItem() {
@@ -617,8 +703,6 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.buildForMerge();
final List<InvoiceItem> expectedResult = Lists.newLinkedList();
- final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
- expectedResult.add(expected1);
final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, cancelDate, endDate, new BigDecimal("-3.48"), currency, initial.getId());
expectedResult.add(repair1);
@@ -651,8 +735,6 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
tree.buildForMerge();
final List<InvoiceItem> expectedResult = Lists.newLinkedList();
- final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, cancelDate, amount1, rate1, currency);
- expectedResult.add(expected1);
final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, cancelDate, endDate, new BigDecimal("-2.00"), currency, initial.getId());
expectedResult.add(repair1);