killbill-memoizeit
Changes
.circleci/config.yml 30(+1 -29)
account/pom.xml 2(+1 -1)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
beatrix/src/main/resources/org/killbill/billing/beatrix/migration/V20180202093543__increase_field_event_json.sql 2(+2 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java 6(+3 -3)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithTags.java 2(+1 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java 46(+25 -21)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java 6(+3 -3)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 50(+25 -25)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java 123(+62 -61)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationVoidInvoice.java 117(+117 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java 26(+13 -13)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java 16(+8 -8)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java 24(+12 -12)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java 10(+5 -5)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java 42(+21 -21)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithWrittenOffTag.java 4(+2 -2)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java 45(+45 -0)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java 10(+5 -5)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 88(+20 -68)
catalog/pom.xml 2(+1 -1)
catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java 33(+27 -6)
catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java 52(+50 -2)
catalog/src/test/resources/catalogTest.xml 34(+32 -2)
currency/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java 7(+4 -3)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java 40(+35 -5)
invoice/pom.xml 2(+1 -1)
invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java 12(+8 -4)
invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java 35(+22 -13)
invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java 28(+17 -11)
invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java 6(+6 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java 131(+131 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java 279(+279 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java 231(+66 -165)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearAggregate.java 49(+49 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearAggregate.java 57(+57 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java 84(+84 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearAggregate.java 25(+25 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java 58(+58 -0)
invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180123114605__invoice_item_quantity_item_details.sql 2(+2 -0)
invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java 4(+2 -2)
invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java 75(+67 -8)
invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java 9(+5 -4)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java 284(+241 -43)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java 756(+695 -61)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java 2(+1 -1)
jaxrs/pom.xml 2(+1 -1)
junction/pom.xml 2(+1 -1)
NEWS 31(+31 -0)
overdue/pom.xml 2(+1 -1)
overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java 19(+14 -5)
overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java 1(+1 -0)
payment/pom.xml 2(+1 -1)
pom.xml 4(+2 -2)
profiles/killbill/pom.xml 2(+1 -1)
profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java 17(+12 -5)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java 2(+2 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java 28(+21 -7)
profiles/killpay/pom.xml 2(+1 -1)
profiles/pom.xml 2(+1 -1)
subscription/pom.xml 2(+1 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 3(+2 -1)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg 13(+4 -9)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java 4(+3 -1)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java 8(+5 -3)
subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java 4(+3 -1)
tenant/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java 41(+34 -7)
util/src/main/resources/org/killbill/billing/util/migration/V20180202093043__increase_field_event_json.sql 4(+4 -0)
util/src/main/resources/trimTenant.sql 75(+75 -0)
Details
.circleci/config.yml 30(+1 -29)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ca7b9fc..ccde348 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -174,46 +174,18 @@ workflows:
version: 2
build-and-test:
jobs:
- - build:
- filters:
- branches:
- only:
- - master
- - work-for-release-0.19.x
- - circle-ci-experiment
+ - build
- test-h2:
requires:
- build
- filters:
- branches:
- only:
- - master
- - circle-ci-experiment
- test-mysql:
requires:
- build
- filters:
- branches:
- only:
- - master
- - work-for-release-0.19.x
- - circle-ci-experiment
- test-postgresql:
requires:
- build
- filters:
- branches:
- only:
- - master
- - circle-ci-experiment
- integration-tests:
requires:
- test-h2
- test-mysql
- test-postgresql
- filters:
- branches:
- only:
- - master
- - work-for-release-0.19.x
- - circle-ci-experiment
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index 0a2e7c5..696355e 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index d937a42..bda9e6d 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-internal-api</artifactId>
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 95fd17f..3e167cf 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql b/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql
index d5ccb71..f0fc5a9 100644
--- a/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql
+++ b/beatrix/src/main/resources/org/killbill/billing/beatrix/ddl.sql
@@ -4,7 +4,7 @@ DROP TABLE IF EXISTS bus_ext_events;
CREATE TABLE bus_ext_events (
record_id serial unique,
class_name varchar(128) NOT NULL,
- event_json varchar(2048) NOT NULL,
+ event_json text NOT NULL,
user_token varchar(36),
created_date datetime NOT NULL,
creating_owner varchar(50) NOT NULL,
@@ -24,7 +24,7 @@ DROP TABLE IF EXISTS bus_ext_events_history;
CREATE TABLE bus_ext_events_history (
record_id serial unique,
class_name varchar(128) NOT NULL,
- event_json varchar(2048) NOT NULL,
+ event_json text NOT NULL,
user_token varchar(36),
created_date datetime NOT NULL,
creating_owner varchar(50) NOT NULL,
diff --git a/beatrix/src/main/resources/org/killbill/billing/beatrix/migration/V20180202093543__increase_field_event_json.sql b/beatrix/src/main/resources/org/killbill/billing/beatrix/migration/V20180202093543__increase_field_event_json.sql
new file mode 100644
index 0000000..ad94662
--- /dev/null
+++ b/beatrix/src/main/resources/org/killbill/billing/beatrix/migration/V20180202093543__increase_field_event_json.sql
@@ -0,0 +1,2 @@
+alter table bus_ext_events modify event_json text not null;
+alter table bus_ext_events_history modify event_json text not null;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index fdc849d..c6ec872 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -697,7 +697,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
// 2012-05-06 => Create an external charge on a new invoice
addDaysAndCheckForCompletion(5);
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), BigDecimal.TEN, Currency.USD);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), BigDecimal.TEN, Currency.USD, null);
invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), false, callContext).get(0);
assertListenerStatus();
invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 6), new LocalDate(2012, 6, 6), InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
@@ -759,7 +759,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
// Now, refund the second (first non-zero dollar) invoice
- final Payment payment = paymentApi.getPayment(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).get(1).getPayments().get(0).getPaymentId(), false, false, PLUGIN_PROPERTIES, callContext);
+ final Payment payment = paymentApi.getPayment(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).get(1).getPayments().get(0).getPaymentId(), false, false, PLUGIN_PROPERTIES, callContext);
refundPaymentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should now be in OD1
checkODState("OD1");
@@ -805,7 +805,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
checkODState(OverdueWrapper.CLEAR_STATE_NAME);
// Now, create a chargeback for the second (first non-zero dollar) invoice
- final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
+ final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
Payment payment = paymentApi.getPayment(invoicePayment.getPaymentId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
payment = createChargeBackAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
// We should now be in OD1
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithTags.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithTags.java
index 797eac4..0bbb25e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithTags.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithTags.java
@@ -82,7 +82,7 @@ public class TestOverdueWithTags extends TestOverdueBase {
// DAY 30 have to get out of trial before first payment
addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice nonNullInvoice = invoices.get(1);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
index 413d676..e0420c5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
@@ -70,7 +70,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of trials for interesting invoices adjustments
@@ -85,7 +85,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
transferApi.transferBundle(account.getId(), newAccount.getId(), "externalKey", clock.getUTCNow(), false, false, callContext);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
@@ -115,7 +115,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
assertListenerStatus();
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -138,7 +138,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
// Day of the transfer
assertEquals(newBCD, (Integer) 3);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
@@ -171,7 +171,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
assertListenerStatus();
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -187,7 +187,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
transferApi.transferBundle(account.getId(), newAccount.getId(), "externalKey", clock.getUTCNow(), false, true, callContext);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
// CHECK OLD AND NEW ACCOUNTS ITEMS
@@ -202,7 +202,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
// CHECK NEW ACCOUNT ITEMS
- invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
index 62a00e8..a0c5599 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCatalogRetireElements.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -22,6 +22,7 @@ import java.util.List;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PhaseType;
@@ -51,7 +52,8 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
return super.getConfigSource("/beatrixCatalogRetireElements.properties");
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testRetirePlan() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v2 starts in 2015-12-01
@@ -71,7 +73,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -95,14 +97,15 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
for (final Invoice invoice : invoices) {
assertEquals(invoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
}
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testRetirePlanWithUncancel() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v2 starts in 2015-12-01
@@ -121,7 +124,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getState(), EntitlementState.ACTIVE);
// Move out a month.
@@ -159,14 +162,15 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
for (final Invoice invoice : invoices) {
assertEquals(invoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
}
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testRetirePlanAfterChange() throws Exception {
// Catalog v1 starts in 2011-01-01
// Catalog v3 starts in 2016-01-01
@@ -187,7 +191,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertNotNull(bpEntitlement);
assertEquals(bpEntitlement.getLastActivePhase().getPhaseType(), PhaseType.TRIAL);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
// Move out after trial (2015-08-04)
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -195,7 +199,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertListenerStatus();
assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 2);
// 2015-08-05
clock.addDays(1);
@@ -209,7 +213,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertListenerStatus();
assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.DISCOUNT);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 3);
// Move out after discount phase (happens on 2015-11-04)
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -252,7 +256,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 9);
}
@@ -277,7 +281,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertNotNull(bpEntitlement);
assertEquals(bpEntitlement.getLastActivePhase().getPhaseType(), PhaseType.TRIAL);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
// Move out after trial (2015-08-04)
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -285,7 +289,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertListenerStatus();
assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.EVERGREEN);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 2);
// 2015-08-05
clock.addDays(1);
@@ -299,7 +303,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertListenerStatus();
assertEquals(entitlementApi.getEntitlementForId(bpEntitlement.getId(), callContext).getLastActivePhase().getPhaseType(), PhaseType.DISCOUNT);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 3);
// Cancel entitlement
bpEntitlement = bpEntitlement.cancelEntitlementWithDate(new LocalDate("2016-05-01"), true, ImmutableList.<PluginProperty>of(), callContext);
@@ -352,7 +356,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 9);
}
@@ -376,7 +380,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -405,7 +409,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
for (final Invoice invoice : invoices) {
assertEquals(invoice.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
@@ -434,7 +438,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
assertListenerStatus();
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
// Move out a month.
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -465,7 +469,7 @@ public class TestCatalogRetireElements extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 5);
assertTrue(invoices.get(0).getInvoiceItems().get(0).getPhaseName().equals("discount-pistol-monthly-trial"));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCustomFieldApi.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCustomFieldApi.java
index c6976cd..0025633 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCustomFieldApi.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestCustomFieldApi.java
@@ -86,7 +86,7 @@ public class TestCustomFieldApi extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
Assert.assertEquals(invoices.size(), 1);
final Invoice invoice = invoices.get(0);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index 2265bbf..fb33ba6 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -21,9 +21,11 @@ package org.killbill.billing.beatrix.integration;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
@@ -47,9 +49,12 @@ import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.mock.MockAccountBuilder;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -670,7 +675,7 @@ public class TestIntegration extends TestIntegrationBase {
entitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
Assert.assertEquals(entitlement.getState(), EntitlementState.BLOCKED);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
assertEquals(invoices.size(), 3);
// Cancel entitlement start of term but with billing policy immediate (ENT_BLOCKED must be after ENT_CANCELLED to trigger the bug)
@@ -688,7 +693,7 @@ public class TestIntegration extends TestIntegrationBase {
assertListenerStatus();
// No new invoices
- invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
assertEquals(invoices.size(), 3);
}
@@ -705,14 +710,14 @@ public class TestIntegration extends TestIntegrationBase {
final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
assertNotNull(invoices);
assertTrue(invoices.size() == 1);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
assertNotNull(invoices);
assertEquals(invoices.size(), 2);
@@ -727,7 +732,7 @@ public class TestIntegration extends TestIntegrationBase {
clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
assertNotNull(invoices);
assertEquals(invoices.size(), 8);
@@ -738,7 +743,7 @@ public class TestIntegration extends TestIntegrationBase {
assertListenerStatus();
}
- invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
assertNotNull(invoices);
assertEquals(invoices.size(), 14);
@@ -928,4 +933,74 @@ public class TestIntegration extends TestIntegrationBase {
startDateBase = endDateBase;
}
}
+
+
+ @Test(groups = "slow")
+ public void testWithDayLightSaving() throws Exception {
+ clock.setTime(new DateTime("2015-09-01T08:01:01.000Z"));
+
+ final DateTimeZone tz = DateTimeZone.forID("America/Juneau");
+ final AccountData accountData = new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
+ .firstNameLength(6)
+ .email(UUID.randomUUID().toString().substring(1, 8))
+ .phone(UUID.randomUUID().toString().substring(1, 8))
+ .migrated(false)
+ .isNotifiedForInvoices(false)
+ .externalKey(UUID.randomUUID().toString().substring(1, 8))
+ .billingCycleDayLocal(1)
+ .currency(Currency.USD)
+ .paymentMethodId(UUID.randomUUID())
+ .referenceTime(clock.getUTCNow())
+ .timeZone(tz)
+ .build();
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
+ //
+ final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ // Check bundle after BP got created otherwise we get an error from auditApi.
+ subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2015, 9, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+ assertListenerStatus();
+
+ //
+ // ADD ADD_ON ON THE SAME DAY
+ //
+ final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(30);
+ assertListenerStatus();
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2015, 10, 1), new LocalDate(2016, 10, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+
+ // 2015-11-1
+ busHandler.pushExpectedEvent(NextEvent.NULL_INVOICE);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // 2015-12-1
+ busHandler.pushExpectedEvent(NextEvent.NULL_INVOICE);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ // We sleep to let system creates lots of notification if an infinite loop was indeed happening
+ Thread.sleep(3000);
+ // And then we check that we only have the expected number of notifications in the history table.
+ final Integer countNotifications = dbi.withHandle(new HandleCallback<Integer>() {
+ @Override
+ public Integer withHandle(final Handle handle) throws Exception {
+
+ List<Map<String, Object>> res = handle.select("select count(*) as count from notifications_history;");
+ final Integer count = Integer.valueOf(res.get(0).get("count").toString());
+ return count;
+ }
+ }
+ );
+ Assert.assertEquals(countNotifications.intValue(), 4);
+ }
+
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index 07a8c84..c3b2036 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -1049,6 +1049,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return isInvoicingSystemEnabled();
}
+ @Override
+ public UsageDetailMode getItemResultBehaviorMode() {
+ return defaultInvoiceConfig.getItemResultBehaviorMode();
+ }
+
+ @Override
+ public UsageDetailMode getItemResultBehaviorMode(final InternalTenantContext tenantContext) {
+ return getItemResultBehaviorMode();
+ }
+
public void setInvoicingSystemEnabled(final boolean invoicingSystemEnabled) {
isInvoicingSystemEnabled = invoicingSystemEnabled;
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
index 559a626..14b2100 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationDryRunInvoice.java
@@ -92,7 +92,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -105,7 +105,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -353,7 +353,7 @@ public class TestIntegrationDryRunInvoice extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 1), new LocalDate(2017, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-19.95")));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index c9c9157..1c3a68b 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -133,7 +133,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -143,7 +143,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
// add create external charge
final LocalDate date = clock.getToday(account.getTimeZone());
final List<InvoiceItem> invoiceItemList = new ArrayList<InvoiceItem>();
- ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(null, account.getId(), subscription.getBundleId(), "", date, date, BigDecimal.TEN, account.getCurrency());
+ ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(null, account.getId(), subscription.getBundleId(), "", date, date, BigDecimal.TEN, account.getCurrency(), null);
invoiceItemList.add(item);
final List<InvoiceItem> draftInvoiceItems = invoiceUserApi.insertExternalCharges(account.getId(), date, invoiceItemList, false, callContext);
@@ -155,7 +155,7 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedDraftInvoices);
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index 2ed5d58..70666e2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -82,7 +82,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
@@ -96,7 +96,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(31);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -115,7 +115,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
BigDecimal.TEN, account.getCurrency(), null, callContext);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -132,7 +132,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
// Force a plan change
//
changeEntitlementAndCheckForCompletion(bpEntitlement, "Blowdart", term, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -175,7 +175,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
@@ -190,7 +190,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
//
changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", term, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -209,7 +209,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(28);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -231,7 +231,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(5);
changeEntitlementAndCheckForCompletion(bpEntitlement, "Blowdart", term, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -259,7 +259,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(1);
changeEntitlementAndCheckForCompletion(bpEntitlement, "Pistol", term, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 5);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -294,7 +294,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 6);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -334,7 +334,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 7);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -393,7 +393,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -402,7 +402,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -415,7 +415,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -432,7 +432,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -454,7 +454,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 5);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -497,7 +497,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
//
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -506,7 +506,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, 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")));
@@ -518,7 +518,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
bpEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -567,7 +567,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
//
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -576,7 +576,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, 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")));
@@ -588,7 +588,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
bpEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2013, 8, 18), new LocalDate(2014, 8, 18), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -634,7 +634,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.BLOCK);
assertNotNull(bpEntitlement);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
@@ -648,7 +648,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addDays(31);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -728,7 +728,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
index 2f139e2..e30bbf2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationParentInvoice.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -27,6 +27,7 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
+import org.joda.time.Minutes;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
@@ -87,7 +88,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
createBaseEntitlementAndCheckForCompletion(child2Account.getId(), "bundleKey2", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// First Parent invoice over TRIAL period
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 1);
Invoice parentInvoice = parentInvoices.get(0);
@@ -103,7 +104,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
final Iterator<NotificationEventWithMetadata<NotificationEvent>> metadataEventIterator = events.iterator();
assertTrue(metadataEventIterator.hasNext());
final NotificationEventWithMetadata<NotificationEvent> notificationEvent = metadataEventIterator.next();
- assertTrue(notificationEvent.getEffectiveDate().compareTo(new DateTime(2015, 5, 15, 23, 59, 59, 0, testTimeZone)) == 0);
+ assertTrue(Math.abs(Minutes.minutesBetween(notificationEvent.getEffectiveDate(), new DateTime(2015, 5, 15, 23, 59, 59, 999, testTimeZone)).getMinutes()) <= 1);
// Moving a day the NotificationQ calls the commitInvoice. No payment is expected
busHandler.pushExpectedEvents(NextEvent.INVOICE);
@@ -121,7 +122,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// Second Parent invoice over Recurring period
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -137,7 +138,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(parentInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(29.95)), 0);
// Check Child Balance. It should be > 0 here because Parent invoice is unpaid yet.
- List<Invoice> child1Invoices = invoiceUserApi.getInvoicesByAccount(child1Account.getId(), false, callContext);
+ List<Invoice> child1Invoices = invoiceUserApi.getInvoicesByAccount(child1Account.getId(), false, false, callContext);
assertEquals(child1Invoices.size(), 2);
// child balance is 0 because parent invoice status is DRAFT at this point
assertEquals(child1Invoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
@@ -151,7 +152,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
// Check Child Balance. It should be = 0 because parent invoice had already paid.
- child1Invoices = invoiceUserApi.getInvoicesByAccount(child1Account.getId(), false, callContext);
+ child1Invoices = invoiceUserApi.getInvoicesByAccount(child1Account.getId(), false, false, callContext);
assertEquals(child1Invoices.size(), 2);
assertTrue(parentInvoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
assertTrue(child1Invoices.get(1).getBalance().compareTo(BigDecimal.ZERO) == 0);
@@ -194,7 +195,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// check parent Invoice with child plan amount
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
Invoice parentInvoice = parentInvoices.get(1);
@@ -211,8 +212,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// check parent invoice. Expected to have the same invoice item with the amount updated
- final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -264,7 +265,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
invoiceUserApi.insertCredit(childAccount.getId(), BigDecimal.TEN, clock.getUTCToday(), Currency.USD, true, "test", callContext);
assertListenerStatus();
- final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 3);
// invoice monthly with credit
@@ -278,7 +279,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
// check parent Invoice with child plan amount
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
Invoice parentInvoice = parentInvoices.get(1);
@@ -294,7 +295,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(1);
assertListenerStatus();
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -345,7 +346,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
invoiceUserApi.insertCredit(childAccount.getId(), BigDecimal.TEN, clock.getUTCToday(), Currency.USD, true, "test", callContext);
assertListenerStatus();
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 3);
// invoice monthly with credit
@@ -355,7 +356,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.valueOf(29.95)), 0);
// check parent Invoice with child plan amount
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
Invoice parentInvoice = parentInvoices.get(1);
@@ -398,8 +399,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(29);
assertListenerStatus();
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// get last child invoice
Invoice childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -483,8 +484,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(1);
assertListenerStatus();
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// get last child invoice
Invoice childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -615,8 +616,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(1);
assertListenerStatus();
- final List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
- final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ final List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
+ final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// get last child invoice
Invoice childInvoice = childInvoices.get(1);
@@ -692,8 +693,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// get last child invoice
Invoice childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -724,7 +725,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
// RECURRING : $ 249.95
// CBA_ADJ $ -233.29
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// invoice 1
childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 2);
@@ -745,7 +746,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(233.29)), 0);
// check if parent invoice was updated
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -791,8 +792,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(1);
assertListenerStatus();
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// get last child invoice
Invoice childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -819,7 +820,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
// Invoice 1: # unchanged
// RECURRING : $ 249.95
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// invoice 1
childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -835,7 +836,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(241.62)), 0);
// check equal parent invoice
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -878,8 +879,8 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(1);
assertListenerStatus();
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// get last child invoice
Invoice childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -906,7 +907,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
// Invoice 1: # unchanged
// RECURRING : $ 249.95
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
// invoice 1
childInvoice = childInvoices.get(1);
assertEquals(childInvoice.getNumberOfItems(), 1);
@@ -922,7 +923,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(241.62)), 0);
// check equal parent invoice
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -945,7 +946,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 5);
childInvoice = childInvoices.get(4);
@@ -956,7 +957,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(-241.62)), 0);
// check equal parent invoice
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 4);
parentInvoice = parentInvoices.get(3);
@@ -1001,7 +1002,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
parentAccountCBA = invoiceUserApi.getAccountCBA(parentAccount.getId(), callContext);
assertEquals(parentAccountCBA.compareTo(BigDecimal.valueOf(250)), 0);
- final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ final List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
final Invoice childInvoice = childInvoices.get(1);
@@ -1012,7 +1013,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(childInvoice.getInvoiceItems().get(1).getAmount().compareTo(BigDecimal.valueOf(-250)), 0);
// check equal parent invoice
- final List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ final List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 1);
final Invoice parentInvoice = parentInvoices.get(0);
@@ -1092,7 +1093,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// First Parent invoice over TRIAL period
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 1);
Invoice parentInvoice = parentInvoices.get(0);
assertEquals(parentInvoice.getNumberOfItems(), 1);
@@ -1101,7 +1102,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(parentInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
// First child invoice over TRIAL period
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 1);
assertEquals(childInvoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
@@ -1116,7 +1117,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// Second Parent invoice over Recurring period
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
assertEquals(parentInvoice.getNumberOfItems(), 1);
@@ -1129,7 +1130,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
parentAccount.getPaymentMethodId());
// Second child invoice over Recurring period
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
assertEquals(childInvoices.get(1).getBalance().compareTo(new BigDecimal("249.95")), 0);
@@ -1179,7 +1180,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// Second Parent invoice over Recurring period
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
// Note that the parent still owns both invoices
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
@@ -1194,7 +1195,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
parentAccount.getPaymentMethodId());
// Second child invoice over Recurring period
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
assertEquals(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
@@ -1207,11 +1208,11 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// No new invoice for the parent
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
// Third child invoice over second Recurring period
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 3);
assertEquals(childInvoices.get(2).getBalance().compareTo(BigDecimal.ZERO), 0);
// Verify the child paid the invoice this time
@@ -1245,7 +1246,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// First child invoice over TRIAL period
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 1);
assertEquals(childInvoices.get(0).getBalance().compareTo(BigDecimal.ZERO), 0);
@@ -1268,11 +1269,11 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// The parent still has no invoice
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 0);
// Second child invoice over Recurring period
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
assertEquals(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
assertEquals(childInvoices.get(1).getPayments().size(), 1);
@@ -1302,7 +1303,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// The parent now owns the invoice
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 1);
final Invoice parentInvoice = parentInvoices.get(0);
assertEquals(parentInvoice.getNumberOfItems(), 1);
@@ -1314,7 +1315,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
parentAccount.getPaymentMethodId());
// Third child invoice over Recurring period
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 3);
assertEquals(childInvoices.get(2).getBalance().compareTo(BigDecimal.ZERO), 0);
}
@@ -1333,7 +1334,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
createBaseEntitlementAndCheckForCompletion(childAccount.getId(), "bundleKey1", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
// First Parent invoice over TRIAL period
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 1);
Invoice parentInvoice = parentInvoices.get(0);
@@ -1358,11 +1359,11 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertListenerStatus();
// Check we don't see yet any new invoice for the parent.
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 1);
// Check we see the new invoice for the child but as DRAFT
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
assertEquals(childInvoices.get(1).getStatus(), InvoiceStatus.DRAFT);
@@ -1372,7 +1373,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
invoiceUserApi.commitInvoice(childInvoices.get(1).getId(), callContext);
assertListenerStatus();
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
parentInvoice = parentInvoices.get(1);
assertEquals(parentInvoice.getNumberOfItems(), 1);
@@ -1385,7 +1386,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(parentInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
// Check Child Balance. It should be > 0 here because Parent invoice is unpaid yet.
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
// child balance is 0 because parent invoice status is DRAFT at this point
assertEquals(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
@@ -1399,7 +1400,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
assertEquals(parentInvoice.getStatus(), InvoiceStatus.COMMITTED);
// Check Child Balance. It should be = 0 because parent invoice had already paid.
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
assertTrue(parentInvoice.getBalance().compareTo(BigDecimal.ZERO) == 0);
assertTrue(childInvoices.get(1).getBalance().compareTo(BigDecimal.ZERO) == 0);
@@ -1441,11 +1442,11 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(29);
assertListenerStatus();
- List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ List<Invoice> childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 2);
// check parent Invoice with child plan amount
- List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ List<Invoice> parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 2);
Invoice parentInvoice = parentInvoices.get(1);
@@ -1471,10 +1472,10 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, callContext);
+ childInvoices = invoiceUserApi.getInvoicesByAccount(childAccount.getId(), false, false, callContext);
assertEquals(childInvoices.size(), 3);
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 3);
@@ -1492,7 +1493,7 @@ public class TestIntegrationParentInvoice extends TestIntegrationBase {
clock.addDays(1);
assertListenerStatus();
- parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, callContext);
+ parentInvoices = invoiceUserApi.getInvoicesByAccount(parentAccount.getId(), false, false, callContext);
assertEquals(parentInvoices.size(), 3);
parentInvoice = parentInvoices.get(2);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationVoidInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationVoidInvoice.java
new file mode 100644
index 0000000..f388e42
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationVoidInvoice.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class TestIntegrationVoidInvoice extends TestIntegrationBase {
+
+ @Test(groups = "slow")
+ public void testVoidInvoice() throws Exception {
+ final int billingDay = 14;
+ final DateTime initialCreationDate = new DateTime(2015, 5, 15, 0, 0, 0, 0, testTimeZone);
+ // set clock to the initial start date
+ clock.setTime(initialCreationDate);
+
+ log.info("Beginning test with BCD of " + billingDay);
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(billingDay));
+
+ add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+ DefaultSubscriptionBase subscription = subscriptionDataFromSubscription(baseEntitlement.getSubscriptionBase());
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 6, 14), new LocalDate(2015, 7, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+ // Move through time and verify we get the same invoice
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE);
+ clock.addDays(30);
+ assertListenerStatus();
+
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
+
+ // Void the invoice
+ invoiceUserApi.voidInvoice(invoices.get(1).getId(), callContext);
+
+ remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
+
+ // Move through time
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(31);
+ assertListenerStatus();
+
+ // get all invoices including the VOIDED; includeVoidedInvoices = true;
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, true, callContext);
+ assertEquals(invoices.size(), 3);
+ // verify integrity of the voided
+ invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
+ assertEquals(invoices.get(1).getStatus(), InvoiceStatus.VOID);
+ // verify that the new invoice contains current and VOIDED charge
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 7, 14), new LocalDate(2015, 8, 14), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+ invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
+
+ // verify that the account balance is fully paid and a payment exists
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+ assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
+
+ final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(payments.size(), 1);
+
+ final Payment payment = payments.get(0);
+ assertTrue(payment.getPurchasedAmount().compareTo(invoices.get(2).getChargedAmount()) == 0);
+
+ // try to void an invoice that is already paid, it should fail.
+ try {
+ invoiceUserApi.voidInvoice(invoices.get(2).getId(), callContext);
+ Assert.fail("Should fail to void invoice that is already paid");
+ } catch (final InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAN_NOT_VOID_INVOICE_THAT_IS_PAID.getCode());
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java
index 3956312..aa882fd 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceDraft.java
@@ -84,7 +84,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK);
assertNotNull(bpEntitlement);
- List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
final Invoice trialInvoice = invoices.get(0);
assertEquals(trialInvoice.getStatus(), InvoiceStatus.DRAFT);
@@ -98,7 +98,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice firstNonTrialInvoice = invoices.get(1);
@@ -115,7 +115,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
@@ -123,7 +123,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
}
@@ -136,7 +136,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK);
assertNotNull(bpEntitlement);
- List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
final Invoice trialInvoice = invoices.get(0);
assertEquals(trialInvoice.getStatus(), InvoiceStatus.DRAFT);
@@ -150,7 +150,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
// Check firstNonTrialInvoice is still in DRAFT
@@ -174,7 +174,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
// Check prev invoice is still in DRAFT
@@ -210,11 +210,11 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
// Create initial DRAFt invoice that will be reused by the system
final LocalDate startDate = clock.getUTCToday();
final LocalDate endDate = startDate.plusDays(5);
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", startDate, endDate, BigDecimal.TEN, Currency.USD);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", startDate, endDate, BigDecimal.TEN, Currency.USD, null);
invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), false, callContext).get(0);
List<Invoice> invoices;
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
assertEquals(invoices.get(0).getStatus(), InvoiceStatus.DRAFT);
@@ -225,7 +225,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
assertNotNull(bpEntitlement);
// Verify we see the new item on our existing DRAFT invoice
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getId(), invoiceId);
assertEquals(invoices.get(0).getInvoiceItems().size(), 2);
@@ -238,7 +238,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
assertListenerStatus();
// Verify again we see the new item on our existing DRAFT invoice
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getId(), invoiceId);
assertEquals(invoices.get(0).getInvoiceItems().size(), 3);
@@ -252,7 +252,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
assertListenerStatus();
// Verify again we see the new item and this time invoice is in COMMITTED
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getId(), invoiceId);
assertEquals(invoices.get(0).getInvoiceItems().size(), 4);
@@ -264,7 +264,7 @@ public class TestIntegrationWithAutoInvoiceDraft extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
assertEquals(invoices.get(1).getInvoiceItems().size(), 1);
assertEquals(invoices.get(1).getStatus(), InvoiceStatus.COMMITTED);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
index 3cd5c9b..abf22b8 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoInvoiceOffTag.java
@@ -76,25 +76,25 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK);
assertNotNull(bpEntitlement);
- Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 0);
clock.addDays(10); // DAY 10 still in trial
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 0);
busHandler.pushExpectedEvents(NextEvent.PHASE);
clock.addDays(30); // DAY 40 out of trial
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 0);
remove_AUTO_INVOICING_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
}
@@ -106,7 +106,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1); // first invoice is generated immediately after creation can't reliably stop it
add_AUTO_INVOICING_OFF_Tag(bpEntitlement.getSubscriptionBase().getBundleId(), ObjectType.BUNDLE);
@@ -115,7 +115,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
clock.addDays(40); // DAY 40 out of trial
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1); //No additional invoices generated
}
@@ -130,7 +130,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement2 = createBaseEntitlementAndCheckForCompletion(account.getId(), "whatever", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement2);
- Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2); // first invoice is generated immediately after creation can't reliably stop it
add_AUTO_INVOICING_OFF_Tag(bpEntitlement.getSubscriptionBase().getBundleId(), ObjectType.BUNDLE);
@@ -139,7 +139,7 @@ public class TestIntegrationWithAutoInvoiceOffTag extends TestIntegrationBase {
clock.addDays(40); // DAY 40 out of trial
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3); // Only one additional invoice generated
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
index afad9a3..397b50e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
@@ -69,7 +69,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
busHandler.pushExpectedEvents(NextEvent.PHASE);
@@ -78,7 +78,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -90,7 +90,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
addDelayBceauseOfLackOfCorrectSynchro();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -110,7 +110,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
busHandler.pushExpectedEvents(NextEvent.PHASE);
@@ -119,7 +119,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -132,7 +132,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
addDelayBceauseOfLackOfCorrectSynchro();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -149,7 +149,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
clock.addDays(nbDaysBeforeRetry + 1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
continue;
@@ -169,7 +169,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
// CREATE FIRST NON NULL INVOICE + FIRST PAYMENT/ATTEMPT -> AUTO_PAY_OFF
@@ -178,7 +178,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
clock.addDays(31); // After trial
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -192,7 +192,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
addDelayBceauseOfLackOfCorrectSynchro();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -210,7 +210,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
clock.addDays(nbDaysBeforeRetry + 1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -226,7 +226,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
addDelayBceauseOfLackOfCorrectSynchro();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
for (Invoice cur : invoices) {
if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
continue;
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
index b128d8a..05f4d5e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithCatalogUpdate.java
@@ -204,7 +204,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
overrides.add(new DefaultPlanPhasePriceOverride(plan.getFinalPhase().getName(), account.getCurrency(), null, BigDecimal.ONE, null));
final Entitlement baseEntitlement = createEntitlement(spec, overrides, true);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, testCallContext);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getChargedAmount().compareTo(BigDecimal.ONE), 0);
@@ -212,7 +212,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, testCallContext);
assertEquals(invoices.size(), 2);
assertEquals(invoices.get(1).getChargedAmount().compareTo(BigDecimal.ONE), 0);
@@ -221,7 +221,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
baseEntitlement.changePlan(spec, null, ImmutableList.<PluginProperty>of(), testCallContext);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, testCallContext);
assertEquals(invoices.size(), 3);
assertEquals(invoices.get(2).getChargedAmount().compareTo(new BigDecimal("9.00")), 0); // 10 (recurring) - 1 (repair)
}
@@ -240,7 +240,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
createEntitlement(spec, null, true);
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, testCallContext);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getChargedAmount().compareTo(BigDecimal.TEN), 0);
assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
@@ -260,7 +260,7 @@ public class TestIntegrationWithCatalogUpdate extends TestIntegrationBase {
assertListenerStatus();
LocalDate endDate = startDate.plusDays(30);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, testCallContext);
assertEquals(invoices.size(), invoiceSize);
expectedInvoices.add(new ExpectedInvoiceItemCheck(startDate, endDate, InvoiceItemType.RECURRING, BigDecimal.TEN));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
index 52a8257..f8f2517 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
@@ -62,7 +62,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -71,7 +71,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addDays(31);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
@@ -84,7 +84,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -118,7 +118,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -127,7 +127,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 1), new LocalDate(2016, 7, 10), InvoiceItemType.RECURRING, new BigDecimal("74.99")));
@@ -140,7 +140,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 10), new LocalDate(2016, 8, 10), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
@@ -151,7 +151,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
//
changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -181,7 +181,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.MONTHLY);
@@ -190,7 +190,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addDays(31);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
@@ -203,7 +203,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.QUARTERLY, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -221,7 +221,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addMonths(2);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -250,7 +250,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -266,7 +266,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
entitlementApi.pause(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -285,7 +285,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
entitlementApi.resume(bpEntitlement.getBundleId(), clock.getUTCNow().toLocalDate(), ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -297,7 +297,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addYears(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 5);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -325,7 +325,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -357,7 +357,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
tagUserApi.removeTag(account.getId(), ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
@@ -374,7 +374,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addYears(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -402,7 +402,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
// Create subscription and check we get the initial invoice for the 30 days TRIAL
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
// Move out of TRIAL and verify we invioice for a full year
@@ -410,7 +410,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addDays(30);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2015, 1, 1), new LocalDate(2016, 1, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -426,7 +426,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
clock.addDays(73);
changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2015, 3, 15), new LocalDate(2016, 3, 1), InvoiceItemType.RECURRING, new BigDecimal("5770.44")),
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithWrittenOffTag.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithWrittenOffTag.java
index 3bfcf02..7dc81fe 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithWrittenOffTag.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithWrittenOffTag.java
@@ -80,14 +80,14 @@ public class TestIntegrationWithWrittenOffTag extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE);
clock.addDays(31);
assertListenerStatus();
- invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
// Tag non $0 invoice with WRITTEN_OFF and remove AUTO_PAY_OFF => System should still not pay anything
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
index c51b21e..1b8b3ed 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceNotifications.java
@@ -22,11 +22,16 @@ import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountData;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
public class TestInvoiceNotifications extends TestIntegrationBase {
@@ -63,4 +68,44 @@ public class TestInvoiceNotifications extends TestIntegrationBase {
// And then verify the invoice is correctly generated
addDaysAndCheckForCompletion(7, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
}
+
+
+ @Test(groups = "slow")
+ public void testInvoiceNotificationWithFutureSubscriptionEvents() throws Exception {
+ clock.setDay(new LocalDate(2018, 1, 31));
+
+ final AccountData accountData = getAccountData(28);
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+
+ final LocalDate billingDate = new LocalDate(2018, 2, 28);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial");
+
+
+ busHandler.pushExpectedEvents(NextEvent.BLOCK);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "bundleKey", null, null, billingDate, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ busHandler.assertListenerStatus();
+
+ // Move to the notification before the start date => 2018, 2, 21
+ addDaysAndCheckForCompletion(21, NextEvent.INVOICE_NOTIFICATION);
+
+ // Move to the start date => 2018, 2, 28
+ addDaysAndCheckForCompletion(7, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+ final LocalDate futureChangeDate = new LocalDate(2018, 3, 28);
+
+ entitlement.changePlanWithDate(new PlanPhaseSpecifier("shotgun-monthly"), null, futureChangeDate, null, callContext);
+ assertListenerStatus();
+
+ // Move to the notification before the start date => 2018, 3, 21
+ addDaysAndCheckForCompletion(21, NextEvent.INVOICE_NOTIFICATION);
+
+
+ // Move to the change date => 2018, 3, 28
+ addDaysAndCheckForCompletion(7, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.NULL_INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+ }
+
+
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index 8bd1845..2d71497 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -107,7 +107,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
bpEntitlement1.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.END_OF_TERM, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
final Invoice thirdInvoice = invoices.get(2);
final InvoiceItem itemForBPEntitlement1 = Iterables.tryFind(thirdInvoice.getInvoiceItems(), new Predicate<InvoiceItem>() {
@Override
@@ -126,7 +126,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
final Invoice fourthInvoice = invoices.get(3);
Assert.assertEquals(fourthInvoice.getInvoiceItems().size(), 1);
@@ -455,7 +455,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.INVOICE);
final LocalDate startDate = clock.getUTCToday();
final LocalDate endDate = startDate.plusDays(5);
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", startDate, endDate, BigDecimal.TEN, Currency.USD);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", startDate, endDate, BigDecimal.TEN, Currency.USD, null);
final InvoiceItem item1 = invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
assertListenerStatus();
// Verify service period for external charge -- seee #151
@@ -553,7 +553,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ?
@@ -647,7 +647,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 1), callContext);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ?
@@ -733,7 +733,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 1), callContext);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ?
@@ -831,7 +831,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 1), callContext);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ?
@@ -938,7 +938,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 1), callContext);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ?
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java
index 200aadf..6c848e7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoiceSystemDisabling.java
@@ -65,14 +65,14 @@ public class TestInvoiceSystemDisabling extends TestIntegrationBase {
NextEvent.TAG);
Assert.assertTrue(parkedAccountsManager.isParked(internalCallContext));
- Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 0);
// Move to end of trial => 2012, 5, 1
addDaysAndCheckForCompletion(30, NextEvent.PHASE);
Assert.assertTrue(parkedAccountsManager.isParked(internalCallContext));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 0);
// Dry-run generation
@@ -84,7 +84,7 @@ public class TestInvoiceSystemDisabling extends TestIntegrationBase {
// Still parked
Assert.assertTrue(parkedAccountsManager.isParked(internalCallContext));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 0);
// Non dry-run generation
@@ -95,7 +95,7 @@ public class TestInvoiceSystemDisabling extends TestIntegrationBase {
// Now unparked
Assert.assertFalse(parkedAccountsManager.isParked(internalCallContext));
invoiceChecker.checkInvoice(invoice, callContext, expected);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1);
invoiceChecker.checkInvoice(account.getId(), 1, callContext, expected);
@@ -103,7 +103,7 @@ public class TestInvoiceSystemDisabling extends TestIntegrationBase {
invoiceConfig.setInvoicingSystemEnabled(true);
addDaysAndCheckForCompletion(31, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
invoiceChecker.checkInvoice(account.getId(),
2,
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index ff77fb8..df2cecf 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -77,7 +77,7 @@ public class TestSubscription extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -86,7 +86,7 @@ public class TestSubscription extends TestIntegrationBase {
clock.addDays(40);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -108,7 +108,7 @@ public class TestSubscription extends TestIntegrationBase {
changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.MONTHLY, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
@@ -117,7 +117,7 @@ public class TestSubscription extends TestIntegrationBase {
//
changeEntitlementAndCheckForCompletion(bpEntitlement, productName, BillingPeriod.ANNUAL, BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 4);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
@@ -155,7 +155,7 @@ public class TestSubscription extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
@@ -164,7 +164,7 @@ public class TestSubscription extends TestIntegrationBase {
clock.addDays(40);
assertListenerStatus();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -175,7 +175,7 @@ public class TestSubscription extends TestIntegrationBase {
// (Note that, the catalog is configured to use CHANGE_OF_PLAN when moving to that plan and Not CHANGE_OF_PRICELIST which has not been implement;
// this is a bit misleading since we are changing pricelist, but in that case pricelist change has no effect)
changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 3);
toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
@@ -255,7 +255,7 @@ public class TestSubscription extends TestIntegrationBase {
assertEquals(addOnEntitlement2.getLastActiveProduct().getName(), "Laser-Scope");
assertEquals(addOnEntitlement2.getLastActiveProductCategory(), ProductCategory.ADD_ON);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 1); // ONLY ONE INVOICE
assertEquals(invoices.get(0).getInvoiceItems().size(), 6);
@@ -281,7 +281,7 @@ public class TestSubscription extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), externalKeyB, "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
final PlanPhaseSpecifier baseSpec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java
index 11dbba0..e1998a0 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestTagApi.java
@@ -88,7 +88,7 @@ public class TestTagApi extends TestIntegrationBase {
final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
assertNotNull(bpEntitlement);
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
Assert.assertEquals(invoices.size(), 1);
final Invoice invoice = invoices.get(0);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
index 49583e8..783f8af 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
@@ -94,7 +94,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 15), InvoiceItemType.RECURRING, new BigDecimal("116.64")));
invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -106,7 +106,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -153,7 +153,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertListenerStatus();
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 15), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-137.07")));
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
@@ -170,7 +170,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
clock.addDays(14);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -185,7 +185,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
clock.addDays(25);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 10), new LocalDate(2016, 8, 10), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 10), new LocalDate(2016, 7, 15), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-41.66")));
invoiceChecker.checkInvoice(invoices.get(4).getId(), callContext, expectedInvoices);
@@ -229,7 +229,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE);
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 5, 10), InvoiceItemType.RECURRING, new BigDecimal("99.99")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-217.70")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 5, 5), InvoiceItemType.CBA_ADJ, new BigDecimal("117.71")));
@@ -241,7 +241,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
clock.addDays(5);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 6, 10), InvoiceItemType.RECURRING, new BigDecimal("599.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 5, 10), InvoiceItemType.CBA_ADJ, new BigDecimal("-117.71")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
@@ -275,7 +275,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
changeEntitlementAndCheckForCompletion(baseEntitlement, "Assault-Rifle", BillingPeriod.MONTHLY, null, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("522.54")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 5), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-217.70")));
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
@@ -288,7 +288,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
clock.addDays(5);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 6, 10), InvoiceItemType.RECURRING, new BigDecimal("599.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-425.77")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
@@ -322,7 +322,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertListenerStatus();
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2017, 5, 10), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 10), new LocalDate(2017, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2340.77")));
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
@@ -339,7 +339,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
clock.addDays(9);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2017, 5, 10), new LocalDate(2018, 5, 10), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -394,7 +394,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 4), new LocalDate(2016, 7, 4), InvoiceItemType.RECURRING, new BigDecimal("1999.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 4), new LocalDate(2016, 7, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1799.96")));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(5).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -404,7 +404,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertListenerStatus();
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 1), new LocalDate(2016, 8, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(6).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -414,7 +414,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertListenerStatus();
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 4), new LocalDate(2016, 8, 4), InvoiceItemType.RECURRING, new BigDecimal("1999.95")));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(7).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -470,7 +470,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-249.95")));
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 16), new LocalDate(2016, 5, 16), InvoiceItemType.CBA_ADJ, new BigDecimal("249.95")));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -488,7 +488,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertListenerStatus();
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 16), new LocalDate(2016, 6, 16), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
}
@@ -529,7 +529,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertListenerStatus();
final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
- List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 1), new LocalDate(2016, 6, 15), InvoiceItemType.RECURRING, new BigDecimal("116.64")));
invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
@@ -538,7 +538,7 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addDays(14);
assertListenerStatus();
- invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, expectedInvoices);
expectedInvoices.clear();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
index 03dcc30..ed205d5 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithInvoicePlugin.java
@@ -110,7 +110,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK);
subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
// Invoice failed to generate
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 0);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 0);
// Verify bus event has moved to the retry service (can't easily check the timestamp unfortunately)
// No future notification at this point (FIXED item, the PHASE event is the trigger for the next one)
@@ -129,7 +129,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
// No notification in the main queue at this point (the PHASE event is the trigger for the next one)
checkNotificationsNoRetry(0);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 1);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 1);
invoiceChecker.checkInvoice(account.getId(),
1,
callContext,
@@ -141,7 +141,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
assertListenerStatus();
checkNotificationsNoRetry(1);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 2);
invoiceChecker.checkInvoice(account.getId(),
2,
callContext,
@@ -155,7 +155,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
assertListenerStatus();
// Invoice failed to generate
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 2);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 2);
// Verify notification has moved to the retry service
checkRetryNotifications("2012-06-01T00:05:00", 1);
@@ -174,7 +174,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
checkNotificationsNoRetry(1);
// Invoice was generated
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 3);
invoiceChecker.checkInvoice(account.getId(),
3,
callContext,
@@ -188,7 +188,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
assertListenerStatus();
// Invoice failed to generate
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 3);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 3);
// Verify notification has moved to the retry service
checkRetryNotifications("2012-07-01T00:05:00", 1);
@@ -200,7 +200,7 @@ public class TestWithInvoicePlugin extends TestIntegrationBase {
assertListenerStatus();
checkNotificationsNoRetry(1);
- assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext).size(), 4);
+ assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext).size(), 4);
}
private void checkRetryBusEvents(final int retryNb, final int expectedFutureInvoiceNotifications) throws NoSuchNotificationQueue {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
index 1ef97cb..a99cba7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
@@ -155,7 +155,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2.95")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(invoices.size(), 2);
final List<InvoiceItem> invoiceItems = invoices.get(1).getInvoiceItems();
final InvoiceItem taxItem = Iterables.tryFind(invoiceItems, new Predicate<InvoiceItem>() {
@@ -284,7 +284,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(invoiceTaxItemId, null, account.getId(), null, "Tax Item", new LocalDate(2012, 4, 1), BigDecimal.ONE, account.getCurrency()));
// Insert external charge autoCommit = false => Invoice will be in DRAFT
- invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCNow().toLocalDate(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(null, account.getId(), null, "foo", new LocalDate(2012, 4, 1), null, new BigDecimal("33.80"), account.getCurrency())), false, callContext);
+ invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCNow().toLocalDate(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(null, account.getId(), null, "foo", new LocalDate(2012, 4, 1), null, new BigDecimal("33.80"), account.getCurrency(), null)), false, callContext);
// Make sure TestInvoicePluginApi **update** the original TAX item
testInvoicePluginApi.addTaxItem(new TaxInvoiceItem(invoiceTaxItemId, null, account.getId(), null, "Tax Item", new LocalDate(2012, 4, 1), new BigDecimal("12.45"), account.getCurrency()));
@@ -294,7 +294,7 @@ public class TestWithTaxItems extends TestIntegrationBase {
clock.addDays(30);
assertListenerStatus();
- final List<Invoice> accountInvoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> accountInvoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
assertEquals(accountInvoices.size(), 2);
// Commit invoice
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
index 50237ba..9d1e86a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
@@ -155,7 +155,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
assertListenerStatus();
// Verify second that there was no repair (so the cancellation did correctly happen on the "2015-12-01")
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
Assert.assertEquals(invoices.size(), 1);
}
@@ -200,7 +200,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
assertListenerStatus();
// Verify second that there was no repair (so the cancellation did correctly happen on the "2015-12-01"
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, callContext);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, false, callContext);
Assert.assertEquals(invoices.size(), 1);
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index b67a99a..607a2b6 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -120,6 +120,26 @@ public class TestConsumableInArrear extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 4, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")),
new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 8, 1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+
+
+ // Add a few more month of usage data and check correctness of invoice: iterate 8 times until 2013-4-1 (prior ANNUAL renewal)
+ LocalDate startDate = new LocalDate(2012, 8, 1);
+ int currentInvoice = 5;
+ for (int i = 0; i < 8; i++) {
+
+ setUsage(aoSubscription.getId(), "bullets", startDate.plusDays(15), 350L, callContext);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), currentInvoice, callContext,
+ new ExpectedInvoiceItemCheck(startDate, startDate.plusMonths(1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+
+
+ startDate = startDate.plusMonths(1);
+ currentInvoice++;
+ }
}
@Test(groups = "slow")
@@ -174,74 +194,6 @@ public class TestConsumableInArrear extends TestIntegrationBase {
assertListenerStatus();
}
- @Test(groups = "slow")
- public void testWithDayLightSaving() throws Exception {
- clock.setTime(new DateTime("2015-09-01T08:01:01.000Z"));
-
- final DateTimeZone tz = DateTimeZone.forID("America/Juneau");
- final AccountData accountData = new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
- .firstNameLength(6)
- .email(UUID.randomUUID().toString().substring(1, 8))
- .phone(UUID.randomUUID().toString().substring(1, 8))
- .migrated(false)
- .isNotifiedForInvoices(false)
- .externalKey(UUID.randomUUID().toString().substring(1, 8))
- .billingCycleDayLocal(1)
- .currency(Currency.USD)
- .paymentMethodId(UUID.randomUUID())
- .referenceTime(clock.getUTCNow())
- .timeZone(tz)
- .build();
- final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
- accountChecker.checkAccount(account.getId(), accountData, callContext);
-
- //
- // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
- //
- final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
- // Check bundle after BP got created otherwise we get an error from auditApi.
- subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
- invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2015, 9, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
- assertListenerStatus();
-
- //
- // ADD ADD_ON ON THE SAME DAY
- //
- final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
- assertListenerStatus();
-
- busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
- clock.addDays(30);
- assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 2, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2015, 10, 1), new LocalDate(2016, 10, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
-
- // 2015-11-1
- busHandler.pushExpectedEvent(NextEvent.NULL_INVOICE);
- clock.addMonths(1);
- assertListenerStatus();
-
- // 2015-12-1
- busHandler.pushExpectedEvent(NextEvent.NULL_INVOICE);
- clock.addMonths(1);
- assertListenerStatus();
-
- // We sleep to let system creates lots of notification if an infinite loop was indeed happening
- Thread.sleep(3000);
- // And then we check that we only have the expected number of notifications in the history table.
- final Integer countNotifications = dbi.withHandle(new HandleCallback<Integer>() {
- @Override
- public Integer withHandle(final Handle handle) throws Exception {
-
- List<Map<String, Object>> res = handle.select("select count(*) as count from notifications_history;");
- final Integer count = Integer.valueOf(res.get(0).get("count").toString());
- return count;
- }
- }
- );
- Assert.assertEquals(countNotifications.intValue(), 4);
- }
-
private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) throws UsageApiException {
final List<UsageRecord> usageRecords = new ArrayList<UsageRecord>();
usageRecords.add(new UsageRecord(startDate, amount));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
index 304a0d4..36b0ee7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
@@ -66,7 +66,7 @@ public class InvoiceChecker {
}
public Invoice checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final CallContext context, final List<ExpectedInvoiceItemCheck> expected) throws InvoiceApiException {
- final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, context);
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId, false, false, context);
//Assert.assertEquals(invoices.size(), invoiceOrderingNumber);
final Invoice invoice = invoices.get(invoiceOrderingNumber - 1);
checkInvoice(invoice.getId(), context, expected);
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 499e1f6..2fdbd1a 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
index f78480d..4729e6d 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalogWithPriceOverride.java
@@ -19,6 +19,7 @@ package org.killbill.billing.catalog;
import java.util.regex.Matcher;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -68,7 +69,6 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
return internalCallContextFactory;
}
-
@Override
public Plan createOrFindCurrentPlan(final PlanSpecifier spec, final PlanPhasePriceOverridesWithCallContext overrides) throws CatalogApiException {
final Plan defaultPlan = super.createOrFindCurrentPlan(spec, null);
@@ -86,8 +86,10 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
public DefaultPlan findCurrentPlan(final String planName) throws CatalogApiException {
final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
if (m.matches()) {
- final InternalTenantContext internalTenantContext = createInternalTenantContext();
- return priceOverride.getOverriddenPlan(planName, this, internalTenantContext);
+ final DefaultPlan plan = maybeGetOverriddenPlan(planName);
+ if (plan != null) {
+ return plan;
+ }
}
return super.findCurrentPlan(planName);
}
@@ -102,13 +104,32 @@ public class StandaloneCatalogWithPriceOverride extends StandaloneCatalog implem
final String planName = DefaultPlanPhase.planName(phaseName);
final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(planName);
if (m.matches()) {
- final InternalTenantContext internalTenantContext = createInternalTenantContext();
- final Plan plan = priceOverride.getOverriddenPlan(planName, this, internalTenantContext);
- return plan.findPhase(phaseName);
+ final DefaultPlan plan = maybeGetOverriddenPlan(planName);
+ if (plan != null) {
+ return plan.findPhase(phaseName);
+ }
}
return super.findCurrentPhase(phaseName);
}
+ private DefaultPlan maybeGetOverriddenPlan(final String planName) throws CatalogApiException {
+ final InternalTenantContext internalTenantContext = createInternalTenantContext();
+
+ try {
+ return priceOverride.getOverriddenPlan(planName, this, internalTenantContext);
+ } catch (final RuntimeException e) {
+ if (e.getCause() == null ||
+ e.getCause().getCause() == null ||
+ !(e.getCause().getCause() instanceof CatalogApiException) ||
+ ((CatalogApiException) e.getCause().getCause()).getCode() != ErrorCode.CAT_NO_SUCH_PLAN.getCode()) {
+ throw e;
+ } else {
+ // Otherwise, ambiguous name? See https://github.com/killbill/killbill/issues/842.
+ return null;
+ }
+ }
+ }
+
private InternalTenantContext createInternalTenantContext() {
return internalCallContextFactory.createInternalTenantContext(tenantRecordId, null);
}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
index 585e2b0..448942f 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogUpdater.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -31,10 +31,12 @@ import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.MutableStaticCatalog;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.catalog.api.SimplePlanDescriptor;
+import org.killbill.billing.catalog.api.StaticCatalog;
import org.killbill.billing.catalog.api.TimeUnit;
import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
import org.killbill.xmlloader.XMLLoader;
@@ -62,6 +64,37 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(catalog.getCurrentPlans().size(), 0);
}
+ @Test(groups = "fast", description = "https://github.com/killbill/killbill/issues/842")
+ public void testCreateAmbiguousPlan() throws CatalogApiException {
+ final DateTime now = clock.getUTCNow();
+ final SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor("foo-monthly-12345", "Foo", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.UNLIMITED, ImmutableList.<String>of());
+
+ final CatalogUpdater catalogUpdater = new CatalogUpdater(now, desc.getCurrency());
+ catalogUpdater.addSimplePlanDescriptor(desc);
+ final StandaloneCatalog catalog = catalogUpdater.getCatalog();
+
+ assertEquals(catalog.getCurrentPlans().size(), 1);
+
+ final StaticCatalog standaloneCatalogWithPriceOverride = new StandaloneCatalogWithPriceOverride(catalog,
+ priceOverride,
+ internalCallContext.getTenantRecordId(),
+ internalCallContextFactory);
+
+ final Plan plan = catalog.findCurrentPlan("foo-monthly-12345");
+ assertEquals(plan.getName(), "foo-monthly-12345");
+
+ // Verify PriceOverride logic
+ final Plan plan2 = standaloneCatalogWithPriceOverride.findCurrentPlan("foo-monthly-12345");
+ assertEquals(plan2.getName(), "foo-monthly-12345");
+
+ final PlanPhase planPhase = catalog.findCurrentPhase("foo-monthly-12345-evergreen");
+ assertEquals(planPhase.getName(), "foo-monthly-12345-evergreen");
+
+ // Verify PriceOverride logic
+ final PlanPhase phase2 = standaloneCatalogWithPriceOverride.findCurrentPhase("foo-monthly-12345-evergreen");
+ assertEquals(phase2.getName(), "foo-monthly-12345-evergreen");
+ }
+
@Test(groups = "fast")
public void testAddNoTrialPlanOnFirstCatalog() throws CatalogApiException {
@@ -102,7 +135,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(priceList.getPlans().iterator().next().getName(), "foo-monthly");
}
-
@Test(groups = "fast")
public void testAddTrialPlanOnFirstCatalog() throws CatalogApiException {
@@ -149,8 +181,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(priceList.getPlans().iterator().next().getName(), "foo-monthly");
}
-
-
@Test(groups = "fast")
public void testAddPlanOnExistingCatalog() throws Exception {
@@ -185,8 +215,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(priceList.getPlans().size(), 4);
}
-
-
@Test(groups = "fast")
public void testAddExistingPlanWithNewCurrency() throws Exception {
final StandaloneCatalog originalCatalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
@@ -231,7 +259,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor("standard-monthly", "Standard", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 0, TimeUnit.DAYS, ImmutableList.<String>of());
addBadSimplePlanDescriptor(catalogUpdater, desc);
-
// Existing Plan has a 30 days trial => try different trial length
desc = new DefaultSimplePlanDescriptor("standard-monthly", "Standard", ProductCategory.BASE, Currency.EUR, BigDecimal.TEN, BillingPeriod.MONTHLY, 14, TimeUnit.DAYS, ImmutableList.<String>of());
addBadSimplePlanDescriptor(catalogUpdater, desc);
@@ -257,11 +284,9 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
addBadSimplePlanDescriptor(catalogUpdater, desc);
}
-
@Test(groups = "fast")
public void testPlanWithNonFinalFixedTermPhase() throws Exception {
-
final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarBasic.xml").toExternalForm(), StandaloneCatalog.class);
final MutableStaticCatalog mutableCatalog = new DefaultMutableStaticCatalog(catalog);
@@ -272,7 +297,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
newProduct.initialize((StandaloneCatalog) mutableCatalog, null);
mutableCatalog.addProduct(newProduct);
-
final DefaultPlanPhase trialPhase = new DefaultPlanPhase();
trialPhase.setPhaseType(PhaseType.TRIAL);
trialPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.DAYS).setNumber(14));
@@ -284,24 +308,22 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
fixedTermPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(3));
fixedTermPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
-
final DefaultPlanPhase evergreenPhase = new DefaultPlanPhase();
evergreenPhase.setPhaseType(PhaseType.EVERGREEN);
evergreenPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(1));
evergreenPhase.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
-
final DefaultPlan newPlan = new DefaultPlan();
newPlan.setName("something-with-fixed-term");
newPlan.setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
newPlan.setProduct(newProduct);
- newPlan.setInitialPhases(new DefaultPlanPhase[] {trialPhase, fixedTermPhase});
+ newPlan.setInitialPhases(new DefaultPlanPhase[]{trialPhase, fixedTermPhase});
newPlan.setFinalPhase(fixedTermPhase);
mutableCatalog.addPlan(newPlan);
newPlan.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
- final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
+ final StandaloneCatalog newCatalog = XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
final DefaultPlan targetPlan = newCatalog.findCurrentPlan("something-with-fixed-term");
Assert.assertEquals(targetPlan.getInitialPhases().length, 2);
@@ -309,8 +331,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
}
-
-
@Test(groups = "fast")
public void testVerifyXML() throws Exception {
@@ -559,7 +579,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
System.err.println(catalogUpdater.getCatalogXML());
}
-
private StandaloneCatalog enhanceOriginalCatalogForInvalidTestCases(final String catalogName) throws Exception {
final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource(catalogName).toExternalForm(), StandaloneCatalog.class);
@@ -592,7 +611,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
mutableCatalog.addPlan(newPlan1);
newPlan1.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
-
final DefaultProduct newProduct2 = new DefaultProduct();
newProduct2.setName("SuperDynamic");
newProduct2.setCatagory(ProductCategory.BASE);
@@ -605,7 +623,6 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
fixedterm2.setDuration(new DefaultDuration().setUnit(TimeUnit.MONTHS).setNumber(3));
fixedterm2.setRecurring(new DefaultRecurring().setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice().setPrices(new DefaultPrice[]{new DefaultPrice().setCurrency(Currency.USD).setValue(BigDecimal.TEN)})));
-
final DefaultPlan newPlan2 = new DefaultPlan();
newPlan2.setName("superdynamic-fixedterm");
newPlan2.setPriceListName(DefaultPriceListSet.DEFAULT_PRICELIST_NAME);
@@ -614,12 +631,10 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
mutableCatalog.addPlan(newPlan2);
newPlan2.initialize((StandaloneCatalog) mutableCatalog, new URI("dummy"));
-
final String newCatalogStr = XMLWriter.writeXML((StandaloneCatalog) mutableCatalog, StandaloneCatalog.class);
return XMLLoader.getObjectFromStream(new URI("dummy"), new ByteArrayInputStream(newCatalogStr.getBytes(Charset.forName("UTF-8"))), StandaloneCatalog.class);
}
-
private void addBadSimplePlanDescriptor(final CatalogUpdater catalogUpdater, final SimplePlanDescriptor desc) {
try {
catalogUpdater.addSimplePlanDescriptor(desc);
@@ -628,4 +643,4 @@ public class TestCatalogUpdater extends CatalogTestSuiteNoDB {
assertEquals(e.getCode(), ErrorCode.CAT_FAILED_SIMPLE_PLAN_VALIDATION.getCode());
}
}
-}
\ No newline at end of file
+}
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java b/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java
index 2a70ca6..571e023 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestStandaloneCatalogWithPriceOverride.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -17,19 +17,67 @@
package org.killbill.billing.catalog;
+import java.math.BigDecimal;
+import java.util.regex.Matcher;
+
import org.killbill.billing.ErrorCode;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
+import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.catalog.api.UsagePriceOverride;
+import org.killbill.billing.catalog.override.DefaultPriceOverride;
import org.killbill.xmlloader.XMLLoader;
+import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;
public class TestStandaloneCatalogWithPriceOverride extends CatalogTestSuiteWithEmbeddedDB {
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/842")
+ public void testCreateAmbiguousPlan() throws Exception {
+ final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
+ final StaticCatalog standaloneCatalogWithPriceOverride = new StandaloneCatalogWithPriceOverride(catalog,
+ priceOverride,
+ internalCallContext.getTenantRecordId(),
+ internalCallContextFactory);
+
+ // Create ambiguous plan name
+ final PlanSpecifier spec = new PlanSpecifier("standard-monthly-67890");
+ final PlanPhasePriceOverridesWithCallContext overrides = Mockito.mock(PlanPhasePriceOverridesWithCallContext.class);
+ Mockito.when(overrides.getCallContext()).thenReturn(callContext);
+ final PlanPhasePriceOverride override = new DefaultPlanPhasePriceOverride("standard-monthly-evergreen", Currency.USD, null, BigDecimal.ONE, ImmutableList.<UsagePriceOverride>of());
+ Mockito.when(overrides.getOverrides()).thenReturn(ImmutableList.of(override));
+ final Plan plan = standaloneCatalogWithPriceOverride.createOrFindCurrentPlan(spec, overrides);
+ Assert.assertTrue(plan.getName().startsWith("standard-monthly-67890-"));
+ final Matcher m = DefaultPriceOverride.CUSTOM_PLAN_NAME_PATTERN.matcher(plan.getName());
+ Assert.assertTrue(m.matches());
+
+ // From the catalog
+ Assert.assertNotNull(catalog.findCurrentPlan("standard-monthly"));
+ Assert.assertNotNull(standaloneCatalogWithPriceOverride.findCurrentPlan("standard-monthly"));
+
+ // Created on the fly
+ try {
+ catalog.findCurrentPlan(plan.getName());
+ Assert.fail();
+ } catch (final CatalogApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_SUCH_PLAN.getCode());
+ }
+ Assert.assertNotNull(standaloneCatalogWithPriceOverride.findCurrentPlan("standard-monthly-1"));
+
+ // From the catalog
+ Assert.assertNotNull(catalog.findCurrentPlan("standard-monthly-12345"));
+ Assert.assertNotNull(standaloneCatalogWithPriceOverride.findCurrentPlan("standard-monthly-12345"));
+ }
+
@Test(groups = "slow")
public void testCreatePlanNoProduct() throws Exception {
final StandaloneCatalog catalog = XMLLoader.getObjectFromString(Resources.getResource("SpyCarAdvanced.xml").toExternalForm(), StandaloneCatalog.class);
catalog/src/test/resources/catalogTest.xml 34(+32 -2)
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index f8d9f93..e4969b1 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright 2010-2013 Ning, Inc.
+ ~ Copyright 2014-2018 Groupon, Inc
+ ~ Copyright 2014-2018 The Billing Project, LLC
~
- ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ The Billing Project licenses this file to you under the Apache License, version 2.0
~ (the "License"); you may not use this file except in compliance with the
~ License. You may obtain a copy of the License at:
~
@@ -42,6 +44,9 @@
</units>
<products>
+ <product name="Knife">
+ <category>STANDALONE</category>
+ </product>
<product name="Blowdart">
<category>BASE</category>
</product>
@@ -191,6 +196,31 @@
</rules>
<plans>
+ <plan name="knife-monthly-notrial">
+ <product>Knife</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>USD</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>29.95</value>
+ </price>
+ <price>
+ <currency>GBP</currency>
+ <value>29.95</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="blowdart-monthly-notrial">
<product>Blowdart</product>
<finalPhase type="EVERGREEN">
@@ -216,7 +246,6 @@
</recurring>
</finalPhase>
</plan>
-
<plan name="pistol-monthly-notrial">
<product>Pistol</product>
<finalPhase type="EVERGREEN">
@@ -1373,6 +1402,7 @@
</childPriceList>
<childPriceList name="notrial">
<plans>
+ <plan>knife-monthly-notrial</plan>
<plan>blowdart-monthly-notrial</plan>
<plan>pistol-monthly-notrial</plan>
</plans>
diff --git a/catalog/src/test/resources/SpyCarAdvanced.xml b/catalog/src/test/resources/SpyCarAdvanced.xml
index a08c425..bfc21c4 100644
--- a/catalog/src/test/resources/SpyCarAdvanced.xml
+++ b/catalog/src/test/resources/SpyCarAdvanced.xml
@@ -247,6 +247,39 @@
</recurring>
</finalPhase>
</plan>
+ <plan name="standard-monthly-12345">
+ <product>Standard</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <recurring>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price>
+ <currency>GBP</currency>
+ <value>75.00</value>
+ </price>
+ <price>
+ <currency>EUR</currency>
+ <value>85.00</value>
+ </price>
+ <price>
+ <currency>USD</currency>
+ <value>100.00</value>
+ </price>
+ <price>
+ <currency>JPY</currency>
+ <value>10.00</value>
+ </price>
+ <price>
+ <currency>BTC</currency>
+ <value>0.1</value>
+ </price>
+ </recurringPrice>
+ </recurring>
+ </finalPhase>
+ </plan>
<plan name="sports-monthly">
<product>Sports</product>
<initialPhases>
@@ -967,5 +1000,10 @@
<plan>cia-sports-monthly</plan>
</plans>
</childPriceList>
+ <childPriceList name="ambiguous">
+ <plans>
+ <plan>standard-monthly-12345</plan>
+ </plans>
+ </childPriceList>
</priceLists>
</catalog>
diff --git a/catalog/src/test/resources/UsageExperimental.xml b/catalog/src/test/resources/UsageExperimental.xml
index 02d2a50..4f9c732 100644
--- a/catalog/src/test/resources/UsageExperimental.xml
+++ b/catalog/src/test/resources/UsageExperimental.xml
@@ -242,7 +242,7 @@
<unit>UNLIMITED</unit>
</duration>
<usages>
- <usage name="consumable-in-arrear-usage1" billingMode="IN_ARREAR" usageType="CONSUMABLE">
+ <usage name="consumable-in-arrear-usage1" billingMode="IN_ARREAR" usageType="CONSUMABLE" tierBlockPolicy="ALL_TIERS">
<billingPeriod>MONTHLY</billingPeriod>
<tiers>
<tier>
currency/pom.xml 2(+1 -1)
diff --git a/currency/pom.xml b/currency/pom.xml
index 6430f67..53f0d55 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-currency</artifactId>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index fd73092..4983880 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 7c84dac..878f1a0 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -111,7 +111,8 @@ public class DefaultEventsStream implements EventsStream {
@Nullable final SubscriptionBaseBundle bundle,
@Nullable final SubscriptionBase baseSubscription,
@Nullable final SubscriptionBase subscription) {
- for (final Object object : new Object[]{account, bundle, baseSubscription, subscription}) {
+ // baseSubscription can be null for STANDALONE products (https://github.com/killbill/killbill/issues/840)
+ for (final Object object : new Object[]{account, bundle, subscription}) {
Preconditions.checkNotNull(object,
"accountId='%s', bundleId='%s', baseSubscriptionId='%s', subscriptionId='%s'",
account != null ? account.getId() : null,
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
index c5cc731..937f095 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlement.java
@@ -24,13 +24,11 @@ import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountApiException;
import org.killbill.billing.api.TestApiListener.NextEvent;
-import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementSourceType;
@@ -140,6 +138,39 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
assertEquals(entitlement3.getState(), EntitlementState.ACTIVE);
}
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/840")
+ public void testUncancelEntitlementFor_STANDALONE_Product() throws AccountApiException, EntitlementApiException {
+ final LocalDate initialDate = new LocalDate(2013, 8, 7);
+ clock.setDay(initialDate);
+
+ final Account account = createAccount(getAccountData(7));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Knife", BillingPeriod.MONTHLY, "notrial", null);
+
+ // Create entitlement and check each field
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, null, null, false, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+ assertEquals(entitlement.getState(), EntitlementState.ACTIVE);
+
+ clock.addDays(5);
+
+ final LocalDate cancelDate = new LocalDate(clock.getUTCToday().plusDays(1));
+ entitlement.cancelEntitlementWithDate(cancelDate, true, ImmutableList.<PluginProperty>of(), callContext);
+
+ final Entitlement entitlement2 = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(entitlement2.getState(), EntitlementState.ACTIVE);
+ assertEquals(entitlement2.getEffectiveEndDate(), cancelDate);
+
+ testListener.pushExpectedEvents(NextEvent.UNCANCEL);
+ entitlement2.uncancelEntitlement(ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ clock.addDays(1);
+ final Entitlement entitlement3 = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
+ assertEquals(entitlement3.getState(), EntitlementState.ACTIVE);
+ }
+
@Test(groups = "slow")
public void testCancelWithEntitlementPolicyEOTAndNOCTD() throws AccountApiException, EntitlementApiException, SubscriptionApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
@@ -283,11 +314,10 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
}
@Test(groups = "slow")
- public void testEntitlementChangePlanOnPendingEntitlement() throws AccountApiException, EntitlementApiException {
+ public void testEntitlementChangePlanOnPendingEntitlement() throws AccountApiException, EntitlementApiException {
final LocalDate initialDate = new LocalDate(2013, 8, 7);
clock.setDay(initialDate);
-
final LocalDate startDate = initialDate.plusDays(10);
final Account account = accountApi.createAccount(getAccountData(7), callContext);
@@ -316,7 +346,7 @@ public class TestDefaultEntitlement extends EntitlementTestSuiteWithEmbeddedDB {
entitlement.changePlanWithDate(spec2, ImmutableList.<PlanPhasePriceOverride>of(), startDate, ImmutableList.<PluginProperty>of(), callContext);
- testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK);
clock.addDays(10);
assertListenerStatus();
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index bba2c29..bd22931 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 3a0bcda..43d1eb9 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -47,9 +47,11 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceApiHelper;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.api.InvoicePayment;
import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.InvoiceUserApi;
import org.killbill.billing.invoice.api.WithAccountLock;
+import org.killbill.billing.invoice.calculator.InvoiceCalculatorUtils;
import org.killbill.billing.invoice.dao.InvoiceDao;
import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
@@ -122,20 +124,20 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
- public List<Invoice> getInvoicesByAccount(final UUID accountId, boolean includesMigrated, final TenantContext context) {
+ public List<Invoice> getInvoicesByAccount(final UUID accountId, boolean includesMigrated, final boolean includeVoidedInvoices, final TenantContext context) {
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(accountId, context);
final List<InvoiceModelDao> invoicesByAccount = includesMigrated ?
- dao.getAllInvoicesByAccount(internalTenantContext) :
- dao.getInvoicesByAccount(internalTenantContext);
+ dao.getAllInvoicesByAccount(includeVoidedInvoices, internalTenantContext) :
+ dao.getInvoicesByAccount(includeVoidedInvoices, internalTenantContext);
return fromInvoiceModelDao(invoicesByAccount, getCatalogSafelyForPrettyNames(internalTenantContext));
}
@Override
- public List<Invoice> getInvoicesByAccount(final UUID accountId, final LocalDate fromDate, final TenantContext context) {
+ public List<Invoice> getInvoicesByAccount(final UUID accountId, final LocalDate fromDate, final boolean includeVoidedInvoices, final TenantContext context) {
final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(accountId, context);
- final List<InvoiceModelDao> invoicesByAccount = dao.getInvoicesByAccount(fromDate, internalTenantContext);
+ final List<InvoiceModelDao> invoicesByAccount = dao.getInvoicesByAccount(includeVoidedInvoices, fromDate, internalTenantContext);
return fromInvoiceModelDao(invoicesByAccount, getCatalogSafelyForPrettyNames(internalTenantContext));
}
@@ -269,7 +271,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
return new ExternalChargeInvoiceItem(externalChargeItem.getId(), externalChargeItem.getInvoiceId(), externalChargeItem.getAccountId(),
externalChargeItem.getDescription(), externalChargeItem.getStartDate(), externalChargeItem.getEndDate(),
- externalChargeItem.getAmount(), externalChargeItem.getCurrency());
+ externalChargeItem.getAmount(), externalChargeItem.getCurrency(), externalChargeItem.getItemDetails());
}
@Override
@@ -333,7 +335,8 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
charge.getAmount(),
charge.getRate(),
charge.getCurrency(),
- charge.getLinkedItemId());
+ charge.getLinkedItemId(),
+ charge.getItemDetails());
invoiceForExternalCharge.addInvoiceItem(externalCharge);
}
@@ -615,4 +618,26 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
return null;
}
}
+
+ @Override
+ public void voidInvoice(final UUID invoiceId, final CallContext context) throws InvoiceApiException {
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(invoiceId, ObjectType.INVOICE, context);
+ Invoice invoice = getInvoice(invoiceId, context);
+
+ if (invoice.getNumberOfPayments() > 0) {
+ canInvoiceBeVoided(invoice);
+ }
+
+ dao.changeInvoiceStatus(invoiceId, InvoiceStatus.VOID, internalCallContext);
+ }
+
+ private void canInvoiceBeVoided(final Invoice invoice) throws InvoiceApiException {
+ final List<InvoicePayment> invoicePayments = invoice.getPayments();
+ final BigDecimal amountPaid = InvoiceCalculatorUtils.computeInvoiceAmountPaid(invoice.getCurrency(), invoicePayments)
+ .add(InvoiceCalculatorUtils.computeInvoiceAmountRefunded(invoice.getCurrency(), invoicePayments));
+
+ if (amountPaid.compareTo(BigDecimal.ZERO) != 0) {
+ throw new InvoiceApiException(ErrorCode.CAN_NOT_VOID_INVOICE_THAT_IS_PAID, invoice.getId().toString());
+ }
+ }
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
index 70f9c13..ea76847 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/config/MultiTenantInvoiceConfig.java
@@ -32,6 +32,7 @@ import org.skife.config.TimeSpan;
public class MultiTenantInvoiceConfig extends MultiTenantConfigBase implements InvoiceConfig {
+
private final InvoiceConfig staticConfig;
@Inject
@@ -163,6 +164,31 @@ public class MultiTenantInvoiceConfig extends MultiTenantConfigBase implements I
}
@Override
+ public UsageDetailMode getItemResultBehaviorMode() {
+ final UsageDetailMode mode = staticConfig.getItemResultBehaviorMode();
+ if (mode == UsageDetailMode.AGGREGATE || mode == UsageDetailMode.DETAIL) {
+ return mode;
+ }
+
+ return UsageDetailMode.AGGREGATE;
+ }
+
+ @Override
+ public UsageDetailMode getItemResultBehaviorMode(final InternalTenantContext tenantContext) {
+ final UsageDetailMode mode = staticConfig.getItemResultBehaviorMode();
+ final String result = getStringTenantConfig("getItemResultBehaviorMode", tenantContext);
+ if (result != null){
+ return UsageDetailMode.valueOf(result);
+ }
+
+ if (mode == UsageDetailMode.AGGREGATE || mode == UsageDetailMode.DETAIL) {
+ return mode;
+ }
+
+ return UsageDetailMode.AGGREGATE;
+ }
+
+ @Override
protected Class<? extends KillbillConfig> getConfigClass() {
return InvoiceConfig.class;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
index 162c204..a2a4cd6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/CBADao.java
@@ -154,7 +154,7 @@ public class CBADao {
// PERF: Computing the invoice balance is difficult to do in the DB, so we effectively need to retrieve all invoices on the account and filter the unpaid ones in memory.
// This should be infrequent though because of the account CBA check above.
- final List<InvoiceModelDao> allInvoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
+ final List<InvoiceModelDao> allInvoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(false, invoicesTags, entitySqlDaoWrapperFactory, context);
final List<InvoiceModelDao> unpaidInvoices = invoiceDaoHelper.getUnpaidInvoicesByAccountFromTransaction(allInvoices, null);
// We order the same os BillingStateCalculator-- should really share the comparator
final List<InvoiceModelDao> orderedUnpaidInvoices = Ordering.from(new Comparator<InvoiceModelDao>() {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 7b17866..8720a38 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -156,7 +156,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
@Override
- public List<InvoiceModelDao> getInvoicesByAccount(final InternalTenantContext context) {
+ public List<InvoiceModelDao> getInvoicesByAccount(final Boolean includeVoidedInvoices, final InternalTenantContext context) {
final List<Tag> invoicesTags = getInvoicesTags(context);
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
@@ -168,7 +168,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
new Predicate<InvoiceModelDao>() {
@Override
public boolean apply(final InvoiceModelDao invoice) {
- return !invoice.isMigrated();
+ return !invoice.isMigrated() &&
+ (includeVoidedInvoices ? true : !InvoiceStatus.VOID.equals(invoice.getStatus()));
}
})));
invoiceDaoHelper.populateChildren(invoices, invoicesTags, entitySqlDaoWrapperFactory, context);
@@ -179,26 +180,26 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
}
@Override
- public List<InvoiceModelDao> getAllInvoicesByAccount(final InternalTenantContext context) {
+ public List<InvoiceModelDao> getAllInvoicesByAccount(final Boolean includeVoidedInvoices, final InternalTenantContext context) {
final List<Tag> invoicesTags = getInvoicesTags(context);
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
@Override
public List<InvoiceModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- return invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
+ return invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(includeVoidedInvoices, invoicesTags, entitySqlDaoWrapperFactory, context);
}
});
}
@Override
- public List<InvoiceModelDao> getInvoicesByAccount(final LocalDate fromDate, final InternalTenantContext context) {
+ public List<InvoiceModelDao> getInvoicesByAccount(final Boolean includeVoidedInvoices, final LocalDate fromDate, final InternalTenantContext context) {
final List<Tag> invoicesTags = getInvoicesTags(context);
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
@Override
public List<InvoiceModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final InvoiceSqlDao invoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
- final List<InvoiceModelDao> invoices = getAllNonMigratedInvoicesByAccountAfterDate(invoiceDao, fromDate, context);
+ final List<InvoiceModelDao> invoices = getAllNonMigratedInvoicesByAccountAfterDate(includeVoidedInvoices, invoiceDao, fromDate, context);
invoiceDaoHelper.populateChildren(invoices, invoicesTags, entitySqlDaoWrapperFactory, context);
return invoices;
@@ -206,12 +207,13 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
});
}
- private List<InvoiceModelDao> getAllNonMigratedInvoicesByAccountAfterDate(final InvoiceSqlDao invoiceSqlDao, final LocalDate fromDate, final InternalTenantContext context) {
+ private List<InvoiceModelDao> getAllNonMigratedInvoicesByAccountAfterDate(final Boolean includeVoidedInvoices, final InvoiceSqlDao invoiceSqlDao, final LocalDate fromDate, final InternalTenantContext context) {
return ImmutableList.<InvoiceModelDao>copyOf(INVOICE_MODEL_DAO_ORDERING.sortedCopy(Iterables.<InvoiceModelDao>filter(invoiceSqlDao.getByAccountRecordId(context),
new Predicate<InvoiceModelDao>() {
@Override
public boolean apply(final InvoiceModelDao invoice) {
- return !invoice.isMigrated() && invoice.getTargetDate().compareTo(fromDate) >= 0;
+ return !invoice.isMigrated() && invoice.getTargetDate().compareTo(fromDate) >= 0 &&
+ (includeVoidedInvoices ? true : !InvoiceStatus.VOID.equals(invoice.getStatus()));
}
})));
}
@@ -455,11 +457,11 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
BigDecimal cba = BigDecimal.ZERO;
BigDecimal accountBalance = BigDecimal.ZERO;
- final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
+ final List<InvoiceModelDao> invoices = invoiceDaoHelper.getAllInvoicesByAccountFromTransaction(false, invoicesTags, entitySqlDaoWrapperFactory, context);
for (final InvoiceModelDao cur : invoices) {
- // Skip DRAFT invoices
- if (cur.getStatus().equals(InvoiceStatus.DRAFT)) {
+ // Skip DRAFT OR VOID invoices
+ if (cur.getStatus().equals(InvoiceStatus.DRAFT) || cur.getStatus().equals(InvoiceStatus.VOID)) {
continue;
}
@@ -467,6 +469,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
cur.getParentInvoice() != null &&
(cur.getParentInvoice().isWrittenOff() ||
cur.getParentInvoice().getStatus() == InvoiceStatus.DRAFT ||
+ cur.getParentInvoice().getStatus() == InvoiceStatus.VOID ||
InvoiceModelDaoHelper.getRawBalanceForRegularInvoice(cur.getParentInvoice()).compareTo(BigDecimal.ZERO) == 0);
@@ -930,7 +933,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
}
- final List<InvoiceModelDao> invoicesFollowing = getAllNonMigratedInvoicesByAccountAfterDate(transactional, invoice.getInvoiceDate(), context);
+ final List<InvoiceModelDao> invoicesFollowing = getAllNonMigratedInvoicesByAccountAfterDate(false, transactional, invoice.getInvoiceDate(), context);
invoiceDaoHelper.populateChildren(invoicesFollowing, invoicesTags, entitySqlDaoWrapperFactory, context);
// The remaining amount to adjust (i.e. the amount of credits used on following invoices)
@@ -1103,10 +1106,11 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
}
- if (invoice.getStatus().equals(newStatus)) {
+ if (invoice.getStatus().equals(newStatus) || invoice.getStatus().equals(InvoiceStatus.VOID)) {
throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_STATUS, newStatus, invoiceId, invoice.getStatus());
}
+
transactional.updateStatus(invoiceId.toString(), newStatus.toString(), context);
cbaDao.doCBAComplexityFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
@@ -1247,7 +1251,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
childCreatedDate.toLocalDate(),
childCreatedDate.toLocalDate(),
accountCBA,
- childAccount.getCurrency());
+ childAccount.getCurrency(),
+ null);
invoiceForExternalCharge.addInvoiceItem(externalChargeItem);
// create credit to parent account
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
index 377b150..205c55f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
@@ -50,9 +50,9 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
InvoiceModelDao getByNumber(Integer number, InternalTenantContext context) throws InvoiceApiException;
- List<InvoiceModelDao> getInvoicesByAccount(InternalTenantContext context);
+ List<InvoiceModelDao> getInvoicesByAccount(final Boolean includeVoidedInvoices, InternalTenantContext context);
- List<InvoiceModelDao> getInvoicesByAccount(LocalDate fromDate, InternalTenantContext context);
+ List<InvoiceModelDao> getInvoicesByAccount(final Boolean includeVoidedInvoices, LocalDate fromDate, InternalTenantContext context);
List<InvoiceModelDao> getInvoicesBySubscription(UUID subscriptionId, InternalTenantContext context);
@@ -71,7 +71,7 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
List<InvoiceModelDao> getUnpaidInvoicesByAccountId(UUID accountId, @Nullable LocalDate upToDate, InternalTenantContext context);
// Include migrated invoices
- List<InvoiceModelDao> getAllInvoicesByAccount(InternalTenantContext context);
+ List<InvoiceModelDao> getAllInvoicesByAccount(final Boolean includeVoidedInvoices, InternalTenantContext context);
InvoicePaymentModelDao postChargeback(UUID paymentId, String chargebackTransactionExternalKey, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index 3343234..0fd4823 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -165,7 +165,7 @@ public class InvoiceDaoHelper {
}
public List<InvoiceModelDao> getUnpaidInvoicesByAccountFromTransaction(final UUID accountId, final List<Tag> invoicesTags, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final LocalDate upToDate, final InternalTenantContext context) {
- final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(invoicesTags, entitySqlDaoWrapperFactory, context);
+ final List<InvoiceModelDao> invoices = getAllInvoicesByAccountFromTransaction(false, invoicesTags, entitySqlDaoWrapperFactory, context);
log.debug("Found invoices={} for accountId={}", invoices, accountId);
return getUnpaidInvoicesByAccountFromTransaction(invoices, upToDate);
}
@@ -250,8 +250,14 @@ public class InvoiceDaoHelper {
}
}
- public List<InvoiceModelDao> getAllInvoicesByAccountFromTransaction(final List<Tag> invoicesTags, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
- final List<InvoiceModelDao> invoices = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getByAccountRecordId(context);
+ public List<InvoiceModelDao> getAllInvoicesByAccountFromTransaction(final Boolean includeVoidedInvoices, final List<Tag> invoicesTags, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
+ final List<InvoiceModelDao> invoices = ImmutableList.<InvoiceModelDao>copyOf(Iterables.<InvoiceModelDao>filter(entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class).getByAccountRecordId(context),
+ new Predicate<InvoiceModelDao>() {
+ @Override
+ public boolean apply(final InvoiceModelDao invoice) {
+ return includeVoidedInvoices ? true : !InvoiceStatus.VOID.equals(invoice.getStatus());
+ }
+ }));
populateChildren(invoices, invoicesTags, entitySqlDaoWrapperFactory, context);
return invoices;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
index e814a69..8104474 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
@@ -47,13 +47,15 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
private BigDecimal rate;
private Currency currency;
private UUID linkedItemId;
+ private Integer quantity;
+ private String itemDetails;
public InvoiceItemModelDao() { /* For the DAO mapper */ }
public InvoiceItemModelDao(final UUID id, final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
final UUID childAccountId, final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
- final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
+ final BigDecimal rate, final Currency currency, final UUID linkedItemId, final Integer quantity, final String itemDetails) {
super(id, createdDate, createdDate);
this.type = type;
this.invoiceId = invoiceId;
@@ -71,6 +73,25 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
this.rate = rate;
this.currency = currency;
this.linkedItemId = linkedItemId;
+ this.quantity = quantity;
+ this.itemDetails = itemDetails;
+ }
+
+ public InvoiceItemModelDao(final UUID id, final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
+ final UUID childAccountId, final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
+ final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
+ final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
+ this(id, createdDate, type, invoiceId, accountId, childAccountId, bundleId, subscriptionId, description, planName, phaseName, usageName,
+ startDate, endDate, amount, rate, currency, linkedItemId, null, null);
+
+ }
+
+ public InvoiceItemModelDao(final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
+ final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
+ final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
+ final BigDecimal rate, final Currency currency, final UUID linkedItemId, final Integer quantity, final String itemDetails) {
+ this(UUIDs.randomUUID(), createdDate, type, invoiceId, accountId, null, bundleId, subscriptionId, description, planName, phaseName, usageName,
+ startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails);
}
public InvoiceItemModelDao(final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
@@ -78,13 +99,13 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
this(UUIDs.randomUUID(), createdDate, type, invoiceId, accountId, null, bundleId, subscriptionId, description, planName, phaseName, usageName,
- startDate, endDate, amount, rate, currency, linkedItemId);
+ startDate, endDate, amount, rate, currency, linkedItemId, null, null);
}
public InvoiceItemModelDao(final InvoiceItem invoiceItem) {
this(invoiceItem.getId(), invoiceItem.getCreatedDate(), invoiceItem.getInvoiceItemType(), invoiceItem.getInvoiceId(), invoiceItem.getAccountId(), invoiceItem.getChildAccountId(), invoiceItem.getBundleId(),
invoiceItem.getSubscriptionId(), invoiceItem.getDescription(), invoiceItem.getPlanName(), invoiceItem.getPhaseName(), invoiceItem.getUsageName(), invoiceItem.getStartDate(), invoiceItem.getEndDate(),
- invoiceItem.getAmount(), invoiceItem.getRate(), invoiceItem.getCurrency(), invoiceItem.getLinkedItemId());
+ invoiceItem.getAmount(), invoiceItem.getRate(), invoiceItem.getCurrency(), invoiceItem.getLinkedItemId(), invoiceItem.getQuantity(), invoiceItem.getItemDetails());
}
/*
@@ -99,128 +120,144 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
return type;
}
+ public void setType(final InvoiceItemType type) {
+ this.type = type;
+ }
+
public UUID getInvoiceId() {
return invoiceId;
}
+ public void setInvoiceId(final UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ }
+
public UUID getAccountId() {
return accountId;
}
+ public void setAccountId(final UUID accountId) {
+ this.accountId = accountId;
+ }
+
public UUID getChildAccountId() {
return childAccountId;
}
+ public void setChildAccountId(final UUID childAccountId) {
+ this.childAccountId = childAccountId;
+ }
+
public UUID getBundleId() {
return bundleId;
}
- public UUID getSubscriptionId() {
- return subscriptionId;
+ public void setBundleId(final UUID bundleId) {
+ this.bundleId = bundleId;
}
- public String getDescription() {
- return description;
+ public UUID getSubscriptionId() {
+ return subscriptionId;
}
- public String getPlanName() {
- return planName;
+ public void setSubscriptionId(final UUID subscriptionId) {
+ this.subscriptionId = subscriptionId;
}
- public String getPhaseName() {
- return phaseName;
+ public String getDescription() {
+ return description;
}
- public String getUsageName() {
- return usageName;
+ public void setDescription(final String description) {
+ this.description = description;
}
- public LocalDate getStartDate() {
- return startDate;
+ public String getPlanName() {
+ return planName;
}
- public LocalDate getEndDate() {
- return endDate;
+ public void setPlanName(final String planName) {
+ this.planName = planName;
}
- public BigDecimal getAmount() {
- return amount;
+ public String getPhaseName() {
+ return phaseName;
}
- public BigDecimal getRate() {
- return rate;
+ public void setPhaseName(final String phaseName) {
+ this.phaseName = phaseName;
}
- public Currency getCurrency() {
- return currency;
+ public String getUsageName() {
+ return usageName;
}
- public UUID getLinkedItemId() {
- return linkedItemId;
+ public void setUsageName(final String usageName) {
+ this.usageName = usageName;
}
- public void setType(final InvoiceItemType type) {
- this.type = type;
+ public LocalDate getStartDate() {
+ return startDate;
}
- public void setInvoiceId(final UUID invoiceId) {
- this.invoiceId = invoiceId;
+ public void setStartDate(final LocalDate startDate) {
+ this.startDate = startDate;
}
- public void setAccountId(final UUID accountId) {
- this.accountId = accountId;
+ public LocalDate getEndDate() {
+ return endDate;
}
- public void setChildAccountId(final UUID childAccountId) {
- this.childAccountId = childAccountId;
+ public void setEndDate(final LocalDate endDate) {
+ this.endDate = endDate;
}
- public void setBundleId(final UUID bundleId) {
- this.bundleId = bundleId;
+ public BigDecimal getAmount() {
+ return amount;
}
- public void setSubscriptionId(final UUID subscriptionId) {
- this.subscriptionId = subscriptionId;
+ public void setAmount(final BigDecimal amount) {
+ this.amount = amount;
}
- public void setDescription(final String description) {
- this.description = description;
+ public BigDecimal getRate() {
+ return rate;
}
- public void setPlanName(final String planName) {
- this.planName = planName;
+ public void setRate(final BigDecimal rate) {
+ this.rate = rate;
}
- public void setPhaseName(final String phaseName) {
- this.phaseName = phaseName;
+ public Currency getCurrency() {
+ return currency;
}
- public void setUsageName(final String usageName) {
- this.usageName = usageName;
+ public void setCurrency(final Currency currency) {
+ this.currency = currency;
}
- public void setStartDate(final LocalDate startDate) {
- this.startDate = startDate;
+ public UUID getLinkedItemId() {
+ return linkedItemId;
}
- public void setEndDate(final LocalDate endDate) {
- this.endDate = endDate;
+ public void setLinkedItemId(final UUID linkedItemId) {
+ this.linkedItemId = linkedItemId;
}
- public void setAmount(final BigDecimal amount) {
- this.amount = amount;
+ public Integer getQuantity() {
+ return quantity;
}
- public void setRate(final BigDecimal rate) {
- this.rate = rate;
+ public void setQuantity(final Integer quantity) {
+ this.quantity = quantity;
}
- public void setCurrency(final Currency currency) {
- this.currency = currency;
+ public String getItemDetails() {
+ return itemDetails;
}
- public void setLinkedItemId(final UUID linkedItemId) {
- this.linkedItemId = linkedItemId;
+ public void setItemDetails(final String itemDetails) {
+ this.itemDetails = itemDetails;
}
@Override
@@ -242,6 +279,8 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
sb.append(", rate=").append(rate);
sb.append(", currency=").append(currency);
sb.append(", linkedItemId=").append(linkedItemId);
+ sb.append(", quantity=").append(quantity);
+ sb.append(", itemDetails=").append(itemDetails);
sb.append('}');
return sb.toString();
}
@@ -308,6 +347,12 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
if (type != that.type) {
return false;
}
+ if (quantity != null ? !quantity.equals(that.quantity) : that.quantity != null) {
+ return false;
+ }
+ if (itemDetails != null ? !itemDetails.equals(that.itemDetails) : that.itemDetails != null) {
+ return false;
+ }
return true;
}
@@ -331,6 +376,8 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
result = 31 * result + (rate != null ? rate.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
result = 31 * result + (linkedItemId != null ? linkedItemId.hashCode() : 0);
+ result = 31 * result + (quantity != null ? quantity.hashCode() : 0);
+ result = 31 * result + (itemDetails != null ? itemDetails.hashCode() : 0);
return result;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java
index 826a1b5..c5db403 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceItemGenerator.java
@@ -48,6 +48,7 @@ public abstract class InvoiceItemGenerator {
private final UUID accountId;
private final String type;
private final Logger delegate;
+ private final boolean enabled;
private StringBuilder logStringBuilder = null;
@@ -56,17 +57,18 @@ public abstract class InvoiceItemGenerator {
this.accountId = accountId;
this.type = type;
this.delegate = delegate;
+ this.enabled = delegate.isDebugEnabled();
}
public void append(final Object event, final Collection<InvoiceItem> items) {
- if (items.isEmpty()) {
+ if (!enabled || items.isEmpty()) {
return;
}
append(event, items.toArray(new InvoiceItem[items.size()]));
}
public void append(final Object event, final InvoiceItem... items) {
- if (items.length == 0) {
+ if (!enabled || items.length == 0) {
return;
}
@@ -80,8 +82,8 @@ public abstract class InvoiceItemGenerator {
}
public void logItems() {
- if (logStringBuilder != null) {
- delegate.info(getLogStringBuilder().toString());
+ if (enabled && logStringBuilder != null) {
+ delegate.debug(getLogStringBuilder().toString());
}
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
index 64e6c4c..5fd4826 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
@@ -45,6 +45,8 @@ import org.killbill.billing.invoice.usage.SubscriptionUsageInArrear;
import org.killbill.billing.invoice.usage.SubscriptionUsageInArrear.SubscriptionUsageInArrearItemsAndNextNotificationDate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.junction.BillingEventSet;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,10 +63,12 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
private static final Logger log = LoggerFactory.getLogger(UsageInvoiceItemGenerator.class);
private final RawUsageOptimizer rawUsageOptimizer;
+ private final InvoiceConfig invoiceConfig;
@Inject
- public UsageInvoiceItemGenerator(final RawUsageOptimizer rawUsageOptimizer) {
+ public UsageInvoiceItemGenerator(final RawUsageOptimizer rawUsageOptimizer, final InvoiceConfig invoiceConfig) {
this.rawUsageOptimizer = rawUsageOptimizer;
+ this.invoiceConfig = invoiceConfig;
}
@Override
@@ -80,7 +84,7 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
try {
// Pretty-print the generated invoice items from the junction events
final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger = new InvoiceItemGeneratorLogger(invoiceId, account.getId(), "usage", log);
-
+ final UsageDetailMode usageDetailMode = invoiceConfig.getItemResultBehaviorMode(internalCallContext);
final LocalDate minBillingEventDate = getMinBillingEventDate(eventSet, internalCallContext);
final List<InvoiceItem> items = Lists.newArrayList();
@@ -115,7 +119,7 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
final UUID subscriptionId = event.getSubscription().getId();
if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) {
- final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate(), internalCallContext);
+ final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate(), usageDetailMode, internalCallContext);
final List<InvoiceItem> usageInArrearItems = perSubscriptionInArrearUsageItems.get(curSubscriptionId);
final SubscriptionUsageInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionUsageInArrear.computeMissingUsageInvoiceItems(usageInArrearItems != null ? usageInArrearItems : ImmutableList.<InvoiceItem>of(), invoiceItemGeneratorLogger);
@@ -128,7 +132,7 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
curEvents.add(event);
}
if (curSubscriptionId != null) {
- final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate(), internalCallContext);
+ final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsageOptimizerResult.getRawUsage(), targetDate, rawUsageOptimizerResult.getRawUsageStartDate(), usageDetailMode, internalCallContext);
final List<InvoiceItem> usageInArrearItems = perSubscriptionInArrearUsageItems.get(curSubscriptionId);
final SubscriptionUsageInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionUsageInArrear.computeMissingUsageInvoiceItems(usageInArrearItems != null ? usageInArrearItems : ImmutableList.<InvoiceItem>of(), invoiceItemGeneratorLogger);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index fabf9c7..ad28373 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -48,10 +48,13 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceNotificationInternalEvent;
+import org.killbill.billing.events.RequestedSubscriptionInternalEvent;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.FutureAccountNotificationsBuilder;
import org.killbill.billing.invoice.api.DefaultInvoiceService;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.DryRunType;
@@ -107,6 +110,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
@@ -168,6 +172,56 @@ public class InvoiceDispatcher {
this.parkedAccountsManager = parkedAccountsManager;
}
+
+ public void processSubscriptionStartRequestedDate(final RequestedSubscriptionInternalEvent transition, final InternalCallContext context) {
+
+ final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(context).getMillis();
+ final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
+ if (!isInvoiceNotificationEnabled) {
+ return;
+ }
+
+ final UUID accountId;
+ try {
+ accountId = subscriptionApi.getAccountIdFromSubscriptionId(transition.getSubscriptionId(), context);
+
+
+ } catch (final SubscriptionBaseApiException e) {
+ log.warn("Failed handling SubscriptionBase change.",
+ new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, transition.getSubscriptionId().toString()));
+ return;
+ }
+
+ try {
+
+ final BillingEventSet billingEvents = billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId, null, context);
+ if (billingEvents.isEmpty()) {
+ return;
+ }
+
+ final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
+ populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);
+
+ final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
+
+ commitInvoiceAndSetFutureNotifications(account, null, notificationsBuilder.build(), context);
+
+ } catch (final SubscriptionBaseApiException e) {
+ log.warn("Failed handling SubscriptionBase change.",
+ new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, transition.getSubscriptionId().toString()));
+ } catch (final AccountApiException e) {
+ log.warn("Failed to retrieve BillingEvents for accountId='{}'", accountId, e);
+ } catch (final CatalogApiException e) {
+ log.warn("Failed to retrieve BillingEvents for accountId='{}'", accountId, e);
+ }
+
+
+
+
+ }
+
+
+
public void processSubscriptionForInvoiceGeneration(final EffectiveSubscriptionInternalEvent transition,
final InternalCallContext context) throws InvoiceApiException {
final UUID subscriptionId = transition.getSubscriptionId();
@@ -279,7 +333,7 @@ public class InvoiceDispatcher {
// (Note that we can't return right away as we send a NullInvoice event)
final List<Invoice> existingInvoices = billingEvents.isAccountAutoInvoiceOff() ?
ImmutableList.<Invoice>of() :
- ImmutableList.<Invoice>copyOf(Collections2.transform(invoiceDao.getInvoicesByAccount(context),
+ ImmutableList.<Invoice>copyOf(Collections2.transform(invoiceDao.getInvoicesByAccount(false, context),
new Function<InvoiceModelDao, Invoice>() {
@Override
public Invoice apply(final InvoiceModelDao input) {
@@ -298,10 +352,11 @@ public class InvoiceDispatcher {
}
}
} else /* Dry run use cases */ {
-
final NotificationQueue notificationQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
- final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+ final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsIterable = notificationQueue.getFutureNotificationForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId());
+ // Copy the results as retrieving the iterator will issue a query each time. This also makes sure the underlying JDBC connection is closed.
+ final List<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = ImmutableList.<NotificationEventWithMetadata<NextBillingDateNotificationKey>>copyOf(futureNotificationsIterable);
final Map<UUID, DateTime> nextScheduledSubscriptionsEventMap = getNextTransitionsForSubscriptions(billingEvents);
@@ -579,8 +634,18 @@ public class InvoiceDispatcher {
return generator.generateInvoice(account, billingEvents, existingInvoices, targetInvoiceId, targetDate, account.getCurrency(), context);
}
+
+
private FutureAccountNotifications createNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final BillingEventSet billingEvents, final InternalCallContext context) {
+ final FutureAccountNotificationsBuilder notificationsBuilder = new FutureAccountNotificationsBuilder();
+ populateNextFutureNotificationDate(invoiceWithMetadata, notificationsBuilder);
+ populateNextFutureDryRunNotificationDate(billingEvents, notificationsBuilder, context);
+ return notificationsBuilder.build();
+ }
+
+
+ private void populateNextFutureNotificationDate(final InvoiceWithMetadata invoiceWithMetadata, final FutureAccountNotificationsBuilder notificationsBuilder) {
final Map<LocalDate, Set<UUID>> notificationListForTrigger = new HashMap<LocalDate, Set<UUID>>();
for (final UUID subscriptionId : invoiceWithMetadata.getPerSubscriptionFutureNotificationDates().keySet()) {
@@ -609,6 +674,13 @@ public class InvoiceDispatcher {
}
}
}
+ notificationsBuilder.setNotificationListForTrigger(notificationListForTrigger);
+ }
+
+ private void populateNextFutureDryRunNotificationDate(final BillingEventSet billingEvents, final FutureAccountNotificationsBuilder notificationsBuilder, final InternalCallContext context) {
+
+
+ final Map<LocalDate, Set<UUID>> notificationListForTrigger = notificationsBuilder.getNotificationListForTrigger();
final long dryRunNotificationTime = invoiceConfig.getDryRunNotificationSchedule(context).getMillis();
final boolean isInvoiceNotificationEnabled = dryRunNotificationTime > 0;
@@ -625,12 +697,12 @@ public class InvoiceDispatcher {
subscriptionsForDryRunDates.addAll(notificationListForTrigger.get(curDate));
}
- final Map<UUID, DateTime> upcomingPhasesForSubscriptions = isInvoiceNotificationEnabled ?
+ final Map<UUID, DateTime> upcomingTransitionsForSubscriptions = isInvoiceNotificationEnabled ?
getNextTransitionsForSubscriptions(billingEvents) :
ImmutableMap.<UUID, DateTime>of();
- for (UUID curId : upcomingPhasesForSubscriptions.keySet()) {
- final LocalDate curDryRunDate = context.toLocalDate(upcomingPhasesForSubscriptions.get(curId).minus(dryRunNotificationTime));
+ for (UUID curId : upcomingTransitionsForSubscriptions.keySet()) {
+ final LocalDate curDryRunDate = context.toLocalDate(upcomingTransitionsForSubscriptions.get(curId).minus(dryRunNotificationTime));
Set<UUID> subscriptionsForDryRunDates = notificationListForDryRun.get(curDryRunDate);
if (subscriptionsForDryRunDates == null) {
subscriptionsForDryRunDates = new HashSet<UUID>();
@@ -639,10 +711,11 @@ public class InvoiceDispatcher {
subscriptionsForDryRunDates.add(curId);
}
}
-
- return new FutureAccountNotifications(notificationListForTrigger, notificationListForDryRun);
+ notificationsBuilder.setNotificationListForDryRun(notificationListForDryRun);
}
+
+
private List<InvoiceItemModelDao> transformToInvoiceModelDao(final List<InvoiceItem> invoiceItems) {
return Lists.transform(invoiceItems,
new Function<InvoiceItem, InvoiceItemModelDao>() {
@@ -778,6 +851,37 @@ public class InvoiceDispatcher {
public Map<LocalDate, Set<UUID>> getNotificationsForDryRun() {
return notificationListForDryRun;
}
+
+
+
+ public static class FutureAccountNotificationsBuilder {
+
+ private Map<LocalDate, Set<UUID>> notificationListForTrigger;
+ private Map<LocalDate, Set<UUID>> notificationListForDryRun;
+
+ public FutureAccountNotificationsBuilder() {
+ }
+
+ public void setNotificationListForTrigger(final Map<LocalDate, Set<UUID>> notificationListForTrigger) {
+ this.notificationListForTrigger = notificationListForTrigger;
+ }
+
+ public void setNotificationListForDryRun(final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
+ this.notificationListForDryRun = notificationListForDryRun;
+ }
+
+ public Map<LocalDate, Set<UUID>> getNotificationListForTrigger() {
+ return MoreObjects.firstNonNull(notificationListForTrigger, ImmutableMap.<LocalDate, Set<UUID>>of());
+ }
+
+ public Map<LocalDate, Set<UUID>> getNotificationListForDryRun() {
+ return MoreObjects.firstNonNull(notificationListForDryRun, ImmutableMap.<LocalDate, Set<UUID>>of());
+ }
+
+ public FutureAccountNotifications build() {
+ return new FutureAccountNotifications(getNotificationListForTrigger(), getNotificationListForDryRun());
+ }
+ }
}
private List<LocalDate> getUpcomingInvoiceCandidateDates(final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
index bf7e879..eee8749 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceListener.java
@@ -29,6 +29,7 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.events.BlockingTransitionInternalEvent;
import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
import org.killbill.billing.events.InvoiceCreationInternalEvent;
+import org.killbill.billing.events.RequestedSubscriptionInternalEvent;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.invoice.api.InvoiceListenerService;
@@ -155,6 +156,15 @@ public class InvoiceListener extends RetryableService implements InvoiceListener
}
}
});
+ subscriberQueueHandler.subscribe(RequestedSubscriptionInternalEvent.class,
+ new SubscriberAction<RequestedSubscriptionInternalEvent>() {
+ @Override
+ public void run(final RequestedSubscriptionInternalEvent event) {
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "SubscriptionBaseTransition", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
+ dispatcher.processSubscriptionStartRequestedDate(event, context);
+ }
+ });
+
this.retryableSubscriber = new RetryableSubscriber(clock, this, subscriberQueueHandler);
}
@@ -190,6 +200,14 @@ public class InvoiceListener extends RetryableService implements InvoiceListener
retryableSubscriber.handleEvent(event);
}
+ @AllowConcurrentEvents
+ @Subscribe
+ public void handleSubscriptionTransition(final RequestedSubscriptionInternalEvent event) {
+ retryableSubscriber.handleEvent(event);
+ }
+
+
+
public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
final InternalCallContext context = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "Next Billing Date", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
try {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
index bc4ccb0..b56e6ac 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoice.java
@@ -261,6 +261,7 @@ public class DefaultInvoice extends EntityBase implements Invoice, Cloneable {
if (isWrittenOff() ||
isMigrationInvoice() ||
getStatus() == InvoiceStatus.DRAFT ||
+ getStatus() == InvoiceStatus.VOID ||
hasZeroParentBalance()) {
return BigDecimal.ZERO;
} else {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
index e2dc4a5..c56e7f0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -30,24 +30,24 @@ import org.killbill.billing.util.UUIDs;
public class ExternalChargeInvoiceItem extends InvoiceItemCatalogBase {
public ExternalChargeInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final String description,
- final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
- this(UUIDs.randomUUID(), invoiceId, accountId, bundleId, description, startDate, endDate, amount, currency);
+ final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, @Nullable final String itemDetails) {
+ this(UUIDs.randomUUID(), invoiceId, accountId, bundleId, description, startDate, endDate, amount, currency, itemDetails);
}
public ExternalChargeInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
- @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
- this(id, null, invoiceId, accountId, bundleId, description, startDate, endDate, amount, currency);
+ @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, @Nullable final String itemDetails) {
+ this(id, null, invoiceId, accountId, bundleId, description, startDate, endDate, amount, currency, itemDetails);
}
public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
- @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
- super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, startDate, endDate, amount, null, currency, null);
+ @Nullable final String description, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, @Nullable final String itemDetails) {
+ super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, startDate, endDate, amount, null, currency, null, null, itemDetails);
}
public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
final String planName, final String phaseName, final String prettyPlanName, final String prettyPhaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
- final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId) {
- super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, linkedItemId);
+ final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId, @Nullable String itemDetails) {
+ super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, prettyPlanName, prettyPhaseName, null, startDate, endDate, amount, rate, currency, linkedItemId, null, itemDetails);
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
index 0e6ea00..b183ebe 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
@@ -53,23 +53,40 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
/* RepairAdjInvoiceItem */
protected final UUID linkedItemId;
+ /* Usage details */
+ protected final Integer quantity;
+ protected final String itemDetails;
public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
@Nullable final UUID subscriptionId, @Nullable final String description,
final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, final UUID reversedItemId) {
- this(id, createdDate, invoiceId, accountId, null, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, reversedItemId);
+ this(id, createdDate, invoiceId, accountId, null, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, reversedItemId, null, null);
+ }
+
+ public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
+ @Nullable final UUID subscriptionId, @Nullable final String description,
+ final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, final UUID reversedItemId,
+ @Nullable final Integer quantity, @Nullable final String itemDetails) {
+ this(id, createdDate, invoiceId, accountId, null, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, reversedItemId, quantity, itemDetails);
}
// For parent invoices
public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID childAccountId,
- final BigDecimal amount, final Currency currency, final String description) {
- this(id, createdDate, invoiceId, accountId, childAccountId, null, null, description, null, null, amount, null, currency, null);
+ final BigDecimal amount, final Currency currency, final String description) {
+ this(id, createdDate, invoiceId, accountId, childAccountId, null, null, description, null, null, amount, null, currency, null, null, null);
+ }
+
+ public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID childAccountId, @Nullable final UUID bundleId,
+ @Nullable final UUID subscriptionId, @Nullable final String description,
+ @Nullable final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency,
+ final UUID reversedItemId) {
+ this(id, createdDate, invoiceId, accountId, childAccountId, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, reversedItemId, null, null);
}
private InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID childAccountId, @Nullable final UUID bundleId,
@Nullable final UUID subscriptionId, @Nullable final String description,
@Nullable final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency,
- final UUID reversedItemId) {
+ final UUID reversedItemId, @Nullable final Integer quantity, @Nullable final String itemDetails) {
super(id, createdDate, createdDate);
this.invoiceId = invoiceId;
this.accountId = accountId;
@@ -83,6 +100,8 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
this.currency = currency;
this.rate = rate;
this.linkedItemId = reversedItemId;
+ this.quantity = quantity;
+ this.itemDetails = itemDetails;
}
@Override
@@ -135,13 +154,11 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
return linkedItemId;
}
-
@Override
public UUID getChildAccountId() {
return childAccountId;
}
-
@Override
public String getPlanName() {
return null;
@@ -172,6 +189,15 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem
return null;
}
+ @Override
+ public Integer getQuantity() {
+ return quantity;
+ }
+
+ @Override
+ public String getItemDetails() {
+ return itemDetails;
+ }
@Override
public boolean equals(final Object o) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java
index 6ad9068..9ad25ce 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemCatalogBase.java
@@ -40,15 +40,30 @@ public abstract class InvoiceItemCatalogBase extends InvoiceItemBase implements
public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
@Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId) {
- this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId);
+ this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId, null, null);
}
+ public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
+ @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+ final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId,
+ @Nullable final Integer quantity, @Nullable final String itemDetails) {
+ this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, null, null, null, startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails);
+ }
public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
@Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
@Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId) {
- super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, linkedItemId);
+ this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, prettyPlanName, prettyPhaseName, prettyUsageName, startDate, endDate, amount, rate, currency, linkedItemId, null, null);
+ }
+
+
+ public InvoiceItemCatalogBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
+ @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+ @Nullable final String prettyPlanName, @Nullable final String prettyPhaseName, @Nullable final String prettyUsageName,
+ final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final UUID linkedItemId,
+ @Nullable final Integer quantity, @Nullable final String itemDetails) {
+ super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, startDate, endDate, amount, rate, currency, linkedItemId, quantity, itemDetails);
this.planName = planName;
this.phaseName = phaseName;
this.usageName = usageName;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
index e8eef20..fc7e21e 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
@@ -69,6 +69,8 @@ public class InvoiceItemFactory {
final BigDecimal rate = invoiceItemModelDao.getRate();
final Currency currency = invoiceItemModelDao.getCurrency();
final UUID linkedItemId = invoiceItemModelDao.getLinkedItemId();
+ final Integer quantity = invoiceItemModelDao.getQuantity();
+ final String itemDetails = invoiceItemModelDao.getItemDetails();
final InvoiceItemType type = invoiceItemModelDao.getType();
@@ -80,7 +82,7 @@ public class InvoiceItemFactory {
final InvoiceItem item;
switch (type) {
case EXTERNAL_CHARGE:
- item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency, linkedItemId);
+ item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, endDate, amount, rate, currency, linkedItemId, itemDetails);
break;
case FIXED:
item = new FixedPriceInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, prettyPlanName, prettyPlanPhaseName, description, startDate, amount, currency);
@@ -101,7 +103,7 @@ public class InvoiceItemFactory {
item = new ItemAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, description, amount, currency, linkedItemId);
break;
case USAGE:
- item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, endDate, description, amount, currency);
+ item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, endDate, description, amount, rate, currency, quantity, itemDetails);
break;
case TAX:
item = new TaxInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, prettyPlanName, prettyPlanPhaseName, prettyUsageName, startDate, description, amount, currency, linkedItemId);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
index 3f66f69..e5841aa 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
@@ -35,14 +35,21 @@ public class UsageInvoiceItem extends InvoiceItemCatalogBase {
public UsageInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
final String planName, final String phaseName, final String usageName,
final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
- this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, null, null, null, startDate, endDate, null, amount, currency);
+ this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, null, null, null, startDate, endDate, null, amount, null, currency, null, null);
+ }
+
+ public UsageInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
+ final String planName, final String phaseName, final String usageName,
+ final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency, @Nullable final Integer quantity, @Nullable final String itemDetails) {
+ this(UUIDs.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, null, null, null, startDate, endDate, null, amount, rate, currency, quantity, itemDetails);
}
public UsageInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
final UUID subscriptionId, final String planName, final String phaseName, final String usageName,
final String prettyPlanName, final String prettyPhaseName, final String prettyUsageName,
- final LocalDate startDate, final LocalDate endDate, @Nullable final String description, final BigDecimal amount, final Currency currency) {
- super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, prettyPlanName, prettyPhaseName, prettyUsageName, startDate, endDate, amount, null, currency, null);
+ final LocalDate startDate, final LocalDate endDate, @Nullable final String description, final BigDecimal amount, final BigDecimal rate,
+ final Currency currency, @Nullable final Integer quantity, @Nullable final String itemDetails) {
+ super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, prettyPlanName, prettyPhaseName, prettyUsageName, startDate, endDate, amount, rate, currency, null, quantity, itemDetails);
}
@Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
index fb269c5..6cfcb83 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/DefaultNextBillingDatePoster.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -19,6 +19,7 @@
package org.killbill.billing.invoice.notification;
import java.io.IOException;
+import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -84,19 +85,27 @@ public class DefaultNextBillingDatePoster implements NextBillingDatePoster {
final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
NotificationEventWithMetadata<NextBillingDateNotificationKey> existingNotificationForEffectiveDate = null;
- for (final NotificationEventWithMetadata<NextBillingDateNotificationKey> input : futureNotifications) {
- final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
- input.getEvent().isDryRunForInvoiceNotification() : false;
-
- final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
- final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
-
- if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
- ((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
- (!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
- existingNotificationForEffectiveDate = input;
+ final Iterator<NotificationEventWithMetadata<NextBillingDateNotificationKey>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<NextBillingDateNotificationKey> input = iterator.next();
+ final boolean isEventDryRunForNotifications = input.getEvent().isDryRunForInvoiceNotification() != null ?
+ input.getEvent().isDryRunForInvoiceNotification() : false;
+
+ final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
+ final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
+
+ if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 &&
+ ((isDryRunForInvoiceNotification && isEventDryRunForNotifications) ||
+ (!isDryRunForInvoiceNotification && !isEventDryRunForNotifications))) {
+ existingNotificationForEffectiveDate = input;
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
if (existingNotificationForEffectiveDate == null) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
index 4a5d7e9..b8c7d76 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/ParentInvoiceCommitmentPoster.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,7 @@
package org.killbill.billing.invoice.notification;
import java.io.IOException;
+import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -58,17 +59,22 @@ public class ParentInvoiceCommitmentPoster {
final Iterable<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> futureNotifications = commitInvoiceQueue.getFutureNotificationFromTransactionForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), entitySqlDaoWrapperFactory.getHandle().getConnection());
boolean existingFutureNotificationWithSameDateAndInvoiceId = false;
- for (final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input : futureNotifications) {
-
-
-
- final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
- final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
-
- if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
- existingFutureNotificationWithSameDateAndInvoiceId = true;
+ final Iterator<NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<ParentInvoiceCommitmentNotificationKey> input = iterator.next();
+ final LocalDate notificationEffectiveLocaleDate = internalCallContext.toLocalDate(futureNotificationTime);
+ final LocalDate eventEffectiveLocaleDate = internalCallContext.toLocalDate(input.getEffectiveDate());
+
+ if (notificationEffectiveLocaleDate.compareTo(eventEffectiveLocaleDate) == 0 && input.getEvent().getUuidKey().equals(invoiceId)) {
+ existingFutureNotificationWithSameDateAndInvoiceId = true;
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
if (!existingFutureNotificationWithSameDateAndInvoiceId) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
index f59f5d8..c411381 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/formatters/DefaultInvoiceItemFormatter.java
@@ -193,6 +193,12 @@ public class DefaultInvoiceItemFormatter implements InvoiceItemFormatter {
}
@Override
+ public Integer getQuantity() { return item.getQuantity(); }
+
+ @Override
+ public String getItemDetails() { return item.getItemDetails(); }
+
+ @Override
public boolean matches(final Object other) {
throw new UnsupportedOperationException();
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
index d194140..aeb8483 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
@@ -124,9 +124,18 @@ public class SubscriptionItemTree {
Preconditions.checkState(!isBuilt);
for (final InvoiceItem item : pendingItemAdj) {
- final Item fullyAdjustedItem = root.addAdjustment(item, targetInvoiceId);
- if (fullyAdjustedItem != null) {
- existingFullyAdjustedItems.add(fullyAdjustedItem);
+ // If the linked item was ignored, ignore this adjustment too
+ final InvoiceItem ignoredLinkedItem = Iterables.tryFind(existingIgnoredItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getId().equals(item.getLinkedItemId());
+ }
+ }).orNull();
+ if (ignoredLinkedItem == null) {
+ final Item fullyAdjustedItem = root.addAdjustment(item, targetInvoiceId);
+ if (fullyAdjustedItem != null) {
+ existingFullyAdjustedItems.add(fullyAdjustedItem);
+ }
}
}
pendingItemAdj.clear();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
new file mode 100644
index 0000000..ed2236c
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.Limit;
+import org.killbill.billing.catalog.api.Tier;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearAggregate;
+import org.killbill.billing.invoice.usage.details.UsageInArrearAggregate;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
+import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearTier;
+
+public class ContiguousIntervalCapacityUsageInArrear extends ContiguousIntervalUsageInArrear {
+
+ private static final Joiner joiner = Joiner.on(", ");
+
+ public ContiguousIntervalCapacityUsageInArrear(final Usage usage,
+ final UUID accountId,
+ final UUID invoiceId,
+ final List<RawUsage> rawSubscriptionUsage,
+ final LocalDate targetDate,
+ final LocalDate rawUsageStartDate,
+ final UsageDetailMode usageDetailMode,
+ final InternalTenantContext internalTenantContext) {
+ super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ }
+
+ @Override
+ protected void populateResults(final LocalDate startDate, final LocalDate endDate, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearAggregate toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) throws InvoiceApiException {
+ // Compute final amount by subtracting amount that was already billed.
+ final BigDecimal amountToBill = toBeBilledUsage.subtract(billedUsage);
+
+ if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
+ final String itemDetails = areAllBilledItemsWithDetails ? toJson(toBeBilledUsageDetails) : null;
+ final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
+ result.add(item);
+ } else if (amountToBill.compareTo(BigDecimal.ZERO) < 0) {
+ throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR,
+ String.format("ILLEGAL INVOICING STATE: Usage period start='%s', end='%s', previously billed amount='%.2f', new proposed amount='%.2f'",
+ startDate, endDate, billedUsage, toBeBilledUsage));
+ }
+ }
+
+ @Override
+ protected UsageInArrearAggregate getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+ return computeToBeBilledCapacityInArrear(rolledUpUnits);
+ }
+
+ private Limit getTierLimit(final Tier tier, final String unitType) {
+ for (final Limit cur : tier.getLimits()) {
+ if (cur.getUnit().getName().equals(unitType)) {
+ return cur;
+ }
+ }
+ Preconditions.checkState(false, "Could not find unit type " + unitType + " in usage tier ");
+ return null;
+ }
+
+ @VisibleForTesting
+ UsageCapacityInArrearAggregate computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits) throws CatalogApiException {
+ Preconditions.checkState(isBuilt.get());
+
+ final List<Tier> tiers = getCapacityInArrearTier(usage);
+
+ final Set<String> perUnitTypeDetailTierLevel = new HashSet<String>();
+ int tierNum = 0;
+ final List<UsageInArrearTierUnitDetail> toBeBilledDetails = Lists.newLinkedList();
+ for (final Tier cur : tiers) {
+ tierNum++;
+ boolean complies = true;
+ for (final RolledUpUnit ro : roUnits) {
+ final Limit tierLimit = getTierLimit(cur, ro.getUnitType());
+ // We ignore the min and only look at the max Limit as the tiers should be contiguous.
+ // Specifying a -1 value for last max tier will make the validation works
+ if (tierLimit.getMax() != (double) -1 && ro.getAmount().doubleValue() > tierLimit.getMax()) {
+ complies = false;
+ } else {
+ if (!perUnitTypeDetailTierLevel.contains(ro.getUnitType())) {
+ toBeBilledDetails.add(new UsageInArrearTierUnitDetail(tierNum, ro.getUnitType(), cur.getRecurringPrice().getPrice(getCurrency()), ro.getAmount().intValue()));
+ perUnitTypeDetailTierLevel.add(ro.getUnitType());
+ }
+ }
+ }
+ if (complies) {
+ return new UsageCapacityInArrearAggregate(toBeBilledDetails, cur.getRecurringPrice().getPrice(getCurrency()));
+ }
+ }
+ // Probably invalid catalog config
+ joiner.join(roUnits);
+ Preconditions.checkState(false, "Could not find tier for usage " + usage.getName() + "matching with data = " + joiner.join(roUnits));
+ return null;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
new file mode 100644
index 0000000..7e85038
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.CatalogApiException;
+import org.killbill.billing.catalog.api.TierBlockPolicy;
+import org.killbill.billing.catalog.api.TieredBlock;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearAggregate;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate;
+import org.killbill.billing.invoice.usage.details.UsageInArrearAggregate;
+import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.usage.api.RolledUpUnit;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+
+import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearTieredBlocks;
+
+public class ContiguousIntervalConsumableUsageInArrear extends ContiguousIntervalUsageInArrear {
+
+ private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalConsumableUsageInArrear.class);
+
+ public ContiguousIntervalConsumableUsageInArrear(final Usage usage,
+ final UUID accountId,
+ final UUID invoiceId,
+ final List<RawUsage> rawSubscriptionUsage,
+ final LocalDate targetDate,
+ final LocalDate rawUsageStartDate,
+ final UsageDetailMode usageDetailMode,
+ final InternalTenantContext internalTenantContext) {
+ super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ }
+
+ @Override
+ protected void populateResults(final LocalDate startDate, final LocalDate endDate, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearAggregate toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) throws InvoiceApiException {
+ // In the case past invoice items showed the details (areAllBilledItemsWithDetails=true), billed usage has already been taken into account
+ // as it part of the reconciliation logic, so no need to subtract it here
+ final BigDecimal amountToBill = (usage.getTierBlockPolicy() == TierBlockPolicy.ALL_TIERS && areAllBilledItemsWithDetails) ? toBeBilledUsage : toBeBilledUsage.subtract(billedUsage);
+
+ if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
+ if (UsageDetailMode.DETAIL == usageDetailMode) {
+
+ for (UsageConsumableInArrearTierUnitAggregate toBeBilledUsageDetail : ((UsageConsumableInArrearAggregate) toBeBilledUsageDetails).getTierDetails()) {
+ final String itemDetails = toJson(toBeBilledUsageDetail);
+ final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), startDate, endDate, toBeBilledUsageDetail.getAmount(), toBeBilledUsageDetail.getTierPrice(), getCurrency(), toBeBilledUsageDetail.getQuantity(), itemDetails);
+ result.add(item);
+ }
+ } else {
+ final String itemDetails = toJson(toBeBilledUsageDetails);
+ final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
+ getPhaseName(), usage.getName(), startDate, endDate, amountToBill, null, getCurrency(), null, itemDetails);
+ result.add(item);
+ }
+ } else if (amountToBill.compareTo(BigDecimal.ZERO) < 0) {
+ throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR,
+ String.format("ILLEGAL INVOICING STATE: Usage period start='%s', end='%s', previously billed amount='%.2f', new proposed amount='%.2f'",
+ startDate, endDate, billedUsage, toBeBilledUsage));
+ }
+ }
+
+ @Override
+ protected UsageInArrearAggregate getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException {
+
+ final Map<String, List<UsageConsumableInArrearTierUnitAggregate>> previousUnitsUsage;
+ if (usageDetailMode == UsageDetailMode.DETAIL || areAllBilledItemsWithDetails) {
+ previousUnitsUsage = new HashMap<String, List<UsageConsumableInArrearTierUnitAggregate>>();
+ for (RolledUpUnit cur : rolledUpUnits) {
+ final List<UsageConsumableInArrearTierUnitAggregate> usageInArrearDetailForUnitType = getBilledDetailsForUnitType(billedItems, cur.getUnitType());
+ previousUnitsUsage.put(cur.getUnitType(), usageInArrearDetailForUnitType);
+ }
+ } else {
+ previousUnitsUsage = ImmutableMap.of();
+ }
+
+ final List<UsageConsumableInArrearTierUnitAggregate> usageConsumableInArrearTierUnitAggregates = new ArrayList<UsageConsumableInArrearTierUnitAggregate>();
+ for (final RolledUpUnit cur : rolledUpUnits) {
+ if (!unitTypes.contains(cur.getUnitType())) {
+ log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
+ continue;
+ }
+ final List<UsageConsumableInArrearTierUnitAggregate> previousUsage = previousUnitsUsage.containsKey(cur.getUnitType()) ? previousUnitsUsage.get(cur.getUnitType()) : ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of();
+
+ final List<UsageConsumableInArrearTierUnitAggregate> toBeBilledConsumableInArrear = computeToBeBilledConsumableInArrear(cur, previousUsage);
+ usageConsumableInArrearTierUnitAggregates.addAll(toBeBilledConsumableInArrear);
+ }
+ final UsageInArrearAggregate toBeBilledUsageDetails = new UsageConsumableInArrearAggregate(usageConsumableInArrearTierUnitAggregates);
+ return toBeBilledUsageDetails;
+ }
+
+ @VisibleForTesting
+ List<UsageConsumableInArrearTierUnitAggregate> getBilledDetailsForUnitType(final Iterable<InvoiceItem> billedItems, final String unitType) {
+
+ // Aggregate on a per-tier level, will return a list with item per level -- for this 'unitType'
+ final Map<Integer, UsageConsumableInArrearTierUnitAggregate> resultMap = new TreeMap<Integer, UsageConsumableInArrearTierUnitAggregate>(Ordering.<Integer>natural());
+
+ List<UsageConsumableInArrearTierUnitAggregate> tierDetails = new ArrayList<UsageConsumableInArrearTierUnitAggregate>();
+ for (final InvoiceItem bi : billedItems) {
+
+ if (usageDetailMode == UsageDetailMode.DETAIL) {
+
+ final UsageConsumableInArrearTierUnitAggregate targetTierUnitDetail = fromJson(bi.getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {});
+ if (!targetTierUnitDetail.getTierUnit().equals(unitType)) {
+ continue;
+ }
+
+ tierDetails.add(new UsageConsumableInArrearTierUnitAggregate(targetTierUnitDetail.getTier(), targetTierUnitDetail.getTierUnit(), bi.getRate(), targetTierUnitDetail.getQuantity(), bi.getQuantity(), bi.getAmount()));
+ } else {
+ final UsageConsumableInArrearAggregate usageDetail = fromJson(bi.getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ tierDetails.addAll(usageDetail.getTierDetails());
+ }
+ }
+
+ for (final UsageConsumableInArrearTierUnitAggregate curDetail : tierDetails) {
+
+ if (curDetail.getTierUnit().equals(unitType)) {
+
+ if (!resultMap.containsKey(curDetail.getTier())) {
+ resultMap.put(curDetail.getTier(), curDetail);
+ } else {
+ final UsageConsumableInArrearTierUnitAggregate perTierDetail = resultMap.get(curDetail.getTier());
+ perTierDetail.updateQuantityAndAmount(curDetail.getQuantity());
+ }
+ }
+ }
+ return ImmutableList.copyOf(resultMap.values());
+ }
+
+ @VisibleForTesting
+ List<UsageConsumableInArrearTierUnitAggregate> computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit, final List<UsageConsumableInArrearTierUnitAggregate> previousUsage) throws CatalogApiException {
+
+ Preconditions.checkState(isBuilt.get());
+ final List<TieredBlock> tieredBlocks = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType());
+
+ switch (usage.getTierBlockPolicy()) {
+ case ALL_TIERS:
+ return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, previousUsage, roUnit.getAmount());
+ case TOP_TIER:
+ return Arrays.asList(computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, previousUsage, roUnit.getAmount()));
+ default:
+ throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy());
+ }
+ }
+
+ List<UsageConsumableInArrearTierUnitAggregate> computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitAggregate> previousUsage, final Long units) throws CatalogApiException {
+
+ List<UsageConsumableInArrearTierUnitAggregate> toBeBilledDetails = Lists.newLinkedList();
+ int remainingUnits = units.intValue();
+ int tierNum = 0;
+
+ final int lastPreviousUsageTier = previousUsage.size(); // we count tier from 1, 2, ...
+ final boolean hasPreviousUsage = lastPreviousUsageTier > 0;
+
+ for (final TieredBlock tieredBlock : tieredBlocks) {
+
+ tierNum++;
+ final int blockTierSize = tieredBlock.getSize().intValue();
+ final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
+ int nbUsedTierBlocks;
+ if (tieredBlock.getMax() != (double) -1 && tmp > tieredBlock.getMax() ) {
+ nbUsedTierBlocks = tieredBlock.getMax().intValue();
+ remainingUnits -= tieredBlock.getMax() * blockTierSize;
+ } else {
+ nbUsedTierBlocks = tmp;
+ remainingUnits = 0;
+ }
+
+ if (nbUsedTierBlocks > 0) {
+ if (hasPreviousUsage) {
+ final Integer previousUsageQuantity = tierNum <= lastPreviousUsageTier ? previousUsage.get(tierNum - 1).getQuantity() : 0;
+ if (tierNum < lastPreviousUsageTier) {
+ Preconditions.checkState(nbUsedTierBlocks == previousUsageQuantity, "Expected usage for tier='%d', unit='%s' to be full, instead found units='[%d/%d]'",
+ tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity);
+ } else {
+ Preconditions.checkState(nbUsedTierBlocks - previousUsageQuantity >= 0, "Expected usage for tier='%d', unit='%s' to contain at least as mush as current usage, instead found units='[%d/%d]",
+ tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity);
+ }
+ nbUsedTierBlocks = nbUsedTierBlocks - previousUsageQuantity;
+ }
+ if (nbUsedTierBlocks > 0) {
+ toBeBilledDetails.add(new UsageConsumableInArrearTierUnitAggregate(tierNum, tieredBlock.getUnit().getName(), tieredBlock.getPrice().getPrice(getCurrency()), blockTierSize, nbUsedTierBlocks));
+ }
+ }
+ }
+ return toBeBilledDetails;
+ }
+
+ UsageConsumableInArrearTierUnitAggregate computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final List<UsageConsumableInArrearTierUnitAggregate> previousUsage, final Long units) throws CatalogApiException {
+
+ int remainingUnits = units.intValue();
+
+ // By default last last tierBlock
+ TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
+ int targetTierNum = tieredBlocks.size();
+ int tierNum = 0;
+ // Loop through all tier block
+ for (final TieredBlock tieredBlock : tieredBlocks) {
+
+ tierNum++;
+ final int blockTierSize = tieredBlock.getSize().intValue();
+ final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
+ if ( tmp > tieredBlock.getMax()) { /* Includes the case where max is unlimited (-1) */
+ remainingUnits -= tieredBlock.getMax() * blockTierSize;
+ } else {
+ targetBlock = tieredBlock;
+ targetTierNum = tierNum;
+ break;
+ }
+ }
+ final int lastBlockTierSize = targetBlock.getSize().intValue();
+ final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
+
+ return new UsageConsumableInArrearTierUnitAggregate(targetTierNum, targetBlock.getUnit().getName(), targetBlock.getPrice().getPrice(getCurrency()), targetBlock.getSize().intValue(), nbBlocks);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ContiguousIntervalConsumableUsageInArrear{");
+ sb.append("transitionTimes=").append(transitionTimes);
+ sb.append(", billingEvents=").append(billingEvents);
+ sb.append(", rawSubscriptionUsage=").append(rawSubscriptionUsage);
+ sb.append(", rawUsageStartDate=").append(rawUsageStartDate);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static <T> T fromJson(String itemDetails, TypeReference<T> ref) {
+ T result = null;
+ if (itemDetails != null) {
+ try {
+ result = objectMapper.readValue(itemDetails, ref);
+ } catch (IOException e) {
+ Preconditions.checkState(false, e.getMessage());
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
index 26f5714..e828849 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java
@@ -34,57 +34,56 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.Limit;
-import org.killbill.billing.catalog.api.Tier;
-import org.killbill.billing.catalog.api.TieredBlock;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.generator.BillingIntervalDetail;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
+import org.killbill.billing.invoice.usage.details.UsageInArrearAggregate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUnit;
import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearTier;
import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearUnitTypes;
-import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearTieredBlocks;
import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearUnitTypes;
-
-
/**
* There is one such class per subscriptionId, matching a given in arrear/consumable usage section and
* referenced through a contiguous list of billing events.
*/
-public class ContiguousIntervalUsageInArrear {
+public abstract class ContiguousIntervalUsageInArrear {
private static final Logger log = LoggerFactory.getLogger(ContiguousIntervalUsageInArrear.class);
- private final List<LocalDate> transitionTimes;
- private final List<BillingEvent> billingEvents;
-
- private final Usage usage;
- private final Set<String> unitTypes;
- private final List<RawUsage> rawSubscriptionUsage;
- private final LocalDate targetDate;
- private final UUID accountId;
- private final UUID invoiceId;
- private final AtomicBoolean isBuilt;
- private final LocalDate rawUsageStartDate;
- private final InternalTenantContext internalTenantContext;
+ protected final List<LocalDate> transitionTimes;
+ protected final List<BillingEvent> billingEvents;
+
+ protected final Usage usage;
+ protected final Set<String> unitTypes;
+ protected final List<RawUsage> rawSubscriptionUsage;
+ protected final LocalDate targetDate;
+ protected final UUID accountId;
+ protected final UUID invoiceId;
+ protected final AtomicBoolean isBuilt;
+ protected final LocalDate rawUsageStartDate;
+ protected final InternalTenantContext internalTenantContext;
+ protected final UsageDetailMode usageDetailMode;
+ protected static final ObjectMapper objectMapper = new ObjectMapper();
public ContiguousIntervalUsageInArrear(final Usage usage,
final UUID accountId,
@@ -92,6 +91,7 @@ public class ContiguousIntervalUsageInArrear {
final List<RawUsage> rawSubscriptionUsage,
final LocalDate targetDate,
final LocalDate rawUsageStartDate,
+ final UsageDetailMode usageDetailMode,
final InternalTenantContext internalTenantContext) {
this.usage = usage;
this.accountId = accountId;
@@ -104,6 +104,7 @@ public class ContiguousIntervalUsageInArrear {
this.billingEvents = Lists.newLinkedList();
this.transitionTimes = Lists.newLinkedList();
this.isBuilt = new AtomicBoolean(false);
+ this.usageDetailMode = usageDetailMode;
}
/**
@@ -159,7 +160,7 @@ public class ContiguousIntervalUsageInArrear {
* @param existingUsage existing on disk usage items for the subscription
* @throws CatalogApiException
*/
- public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException {
+ public UsageInArrearItemsAndNextNotificationDate computeMissingItemsAndNextNotificationDate(final List<InvoiceItem> existingUsage) throws CatalogApiException, InvoiceApiException {
Preconditions.checkState(isBuilt.get());
@@ -182,41 +183,41 @@ public class ContiguousIntervalUsageInArrear {
}
final List<RolledUpUsage> allUsage = getRolledUpUsage();
+ // Each RolledUpUsage 'ru' is for a specific time period and across all units
for (final RolledUpUsage ru : allUsage) {
- BigDecimal toBeBilledUsage = BigDecimal.ZERO;
- if (usage.getUsageType() == UsageType.CAPACITY) {
- toBeBilledUsage = computeToBeBilledCapacityInArrear(ru.getRolledUpUnits());
- } else /* UsageType.CONSUMABLE */{
-
- // Compute total price amount that should be billed for that period of time (and usage section) across unitTypes.
- for (final RolledUpUnit cur : ru.getRolledUpUnits()) {
- if (!unitTypes.contains(cur.getUnitType())) {
- log.warn("ContiguousIntervalConsumableInArrear is skipping unitType " + cur.getUnitType());
- continue;
- }
-
- final BigDecimal toBeBilledForUnit = computeToBeBilledConsumableInArrear(cur);
- toBeBilledUsage = toBeBilledUsage.add(toBeBilledForUnit);
- }
- }
- // Retrieves current price amount billed for that period of time (and usage section)
+ //
+ // Previously billed items:
+ //
+ // 1. Retrieves current price amount billed for that period of time (and usage section)
final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStart(), ru.getEnd(), existingUsage);
+ // 2. Verify whether previously built items have the item_details section
+ final boolean areAllBilledItemsWithDetails = areAllBilledItemsWithDetails(billedItems);
+ // 3. Computes total billed usage amount
final BigDecimal billedUsage = computeBilledUsage(billedItems);
- // Compare the two and add the missing piece if required.
- if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
- final BigDecimal amountToBill = toBeBilledUsage.subtract(billedUsage);
- if (amountToBill.compareTo(BigDecimal.ZERO) > 0) {
- final InvoiceItem item = new UsageInvoiceItem(invoiceId, accountId, getBundleId(), getSubscriptionId(), getPlanName(),
- getPhaseName(), usage.getName(), ru.getStart(), ru.getEnd(), amountToBill, getCurrency());
- result.add(item);
- }
- }
+ final List<RolledUpUnit> rolledUpUnits = ru.getRolledUpUnits();
+
+ final UsageInArrearAggregate toBeBilledUsageDetails = getToBeBilledUsageDetails(rolledUpUnits, billedItems, areAllBilledItemsWithDetails);
+ final BigDecimal toBeBilledUsage = toBeBilledUsageDetails.getAmount();
+ populateResults(ru.getStart(), ru.getEnd(), billedUsage, toBeBilledUsage, toBeBilledUsageDetails, areAllBilledItemsWithDetails, result);
}
+ final LocalDate nextNotificationDate = computeNextNotificationDate();
+ return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationDate);
+ }
+
+ protected abstract void populateResults(final LocalDate startDate, final LocalDate endDate, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearAggregate toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final List<InvoiceItem> result) throws InvoiceApiException;
- final LocalDate nextNotificationdate = computeNextNotificationDate();
- return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationdate);
+ protected abstract UsageInArrearAggregate getToBeBilledUsageDetails(final List<RolledUpUnit> rolledUpUnits, final Iterable<InvoiceItem> billedItems, final boolean areAllBilledItemsWithDetails) throws CatalogApiException;
+
+ private boolean areAllBilledItemsWithDetails(final Iterable<InvoiceItem> billedItems) {
+ boolean atLeastOneItemWithoutDetails = Iterables.any(billedItems, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getItemDetails() == null || input.getItemDetails().isEmpty();
+ }
+ });
+ return !atLeastOneItemWithoutDetails;
}
private LocalDate computeNextNotificationDate() {
@@ -285,7 +286,7 @@ public class ContiguousIntervalUsageInArrear {
if (prevRawUsage != null) {
if (prevRawUsage.getDate().compareTo(prevDate) >= 0 && prevRawUsage.getDate().compareTo(curDate) < 0) {
final Long currentAmount = perRangeUnitToAmount.get(prevRawUsage.getUnitType());
- final Long updatedAmount = computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
+ final Long updatedAmount = computeUpdatedAmount(currentAmount, prevRawUsage.getAmount());
perRangeUnitToAmount.put(prevRawUsage.getUnitType(), updatedAmount);
prevRawUsage = null;
}
@@ -306,7 +307,7 @@ public class ContiguousIntervalUsageInArrear {
}
final Long currentAmount = perRangeUnitToAmount.get(curRawUsage.getUnitType());
- final Long updatedAmount = computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
+ final Long updatedAmount = computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
perRangeUnitToAmount.put(curRawUsage.getUnitType(), updatedAmount);
}
}
@@ -344,117 +345,6 @@ public class ContiguousIntervalUsageInArrear {
}
}
-
- private Limit getTierLimit(final Tier tier, final String unitType) {
- for (final Limit cur : tier.getLimits()) {
- if (cur.getUnit().getName().equals(unitType)) {
- return cur;
- }
- }
- Preconditions.checkState(false, "Could not find unit type " + unitType + " in usage tier ");
- return null;
- }
-
- /**
- * @param roUnits the list of rolled up units for the period
- * @return the price amount that should be billed for that period/unitType
- * @throws CatalogApiException
- */
- @VisibleForTesting
- BigDecimal computeToBeBilledCapacityInArrear(final List<RolledUpUnit> roUnits) throws CatalogApiException {
- Preconditions.checkState(isBuilt.get());
-
- final List<Tier> tiers = getCapacityInArrearTier(usage);
-
- for (final Tier cur : tiers) {
- boolean complies = true;
- for (final RolledUpUnit ro : roUnits) {
- final Limit tierLimit = getTierLimit(cur, ro.getUnitType());
- // We ignore the min and only look at the max Limit as the tiers should be contiguous.
- // Specifying a -1 value for last max tier will make the validation works
- if (tierLimit.getMax() != (double) -1 && ro.getAmount().doubleValue() > tierLimit.getMax()) {
- complies = false;
- break;
- }
- }
- if (complies) {
- return cur.getRecurringPrice().getPrice(getCurrency());
- }
- }
- // Probably invalid catalog config
- final Joiner joiner = Joiner.on(", ");
- joiner.join(roUnits);
- Preconditions.checkState(false, "Could not find tier for usage " + usage.getName()+ "matching with data = " + joiner.join(roUnits));
- return null;
- }
-
- /**
- * @param roUnit the rolled up unit for the period
- * @return the price amount that should be billed for that period/unitType
- * @throws CatalogApiException
- */
- @VisibleForTesting
- BigDecimal computeToBeBilledConsumableInArrear(final RolledUpUnit roUnit) throws CatalogApiException {
-
- Preconditions.checkState(isBuilt.get());
- final List<TieredBlock> tieredBlocks = getConsumableInArrearTieredBlocks(usage, roUnit.getUnitType());
-
- switch (usage.getTierBlockPolicy()) {
- case ALL_TIERS:
- return computeToBeBilledConsumableInArrearWith_ALL_TIERS(tieredBlocks, roUnit.getAmount());
- case TOP_TIER:
- return computeToBeBilledConsumableInArrearWith_TOP_TIER(tieredBlocks, roUnit.getAmount());
- default:
- throw new IllegalStateException("Unknown TierBlockPolicy " + usage.getTierBlockPolicy());
- }
- }
-
-
- BigDecimal computeToBeBilledConsumableInArrearWith_ALL_TIERS(final List<TieredBlock> tieredBlocks, final Long units) throws CatalogApiException {
-
- BigDecimal result = BigDecimal.ZERO;
- int remainingUnits = units.intValue();
- for (final TieredBlock tieredBlock : tieredBlocks) {
-
- final int blockTierSize = tieredBlock.getSize().intValue();
- final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
- final int nbUsedTierBlocks;
- if (tmp > tieredBlock.getMax()) {
- nbUsedTierBlocks = tieredBlock.getMax().intValue();
- remainingUnits -= tieredBlock.getMax() * blockTierSize;
- } else {
- nbUsedTierBlocks = tmp;
- remainingUnits = 0;
- }
- result = result.add(tieredBlock.getPrice().getPrice(getCurrency()).multiply(new BigDecimal(nbUsedTierBlocks)));
- }
- return result;
- }
-
- BigDecimal computeToBeBilledConsumableInArrearWith_TOP_TIER(final List<TieredBlock> tieredBlocks, final Long units) throws CatalogApiException {
-
- int remainingUnits = units.intValue();
-
- // By default last last tierBlock
- TieredBlock targetBlock = tieredBlocks.get(tieredBlocks.size() - 1);
- // Loop through all tier block
- for (final TieredBlock tieredBlock : tieredBlocks) {
-
- final int blockTierSize = tieredBlock.getSize().intValue();
- final int tmp = remainingUnits / blockTierSize + (remainingUnits % blockTierSize == 0 ? 0 : 1);
- if (tmp > tieredBlock.getMax()) {
- remainingUnits -= tieredBlock.getMax() * blockTierSize;
- } else {
- targetBlock = tieredBlock;
- break;
- }
- }
- final int lastBlockTierSize = targetBlock.getSize().intValue();
- final int nbBlocks = units.intValue() / lastBlockTierSize + (units.intValue() % lastBlockTierSize == 0 ? 0 : 1);
- return targetBlock.getPrice().getPrice(getCurrency()).multiply(new BigDecimal(nbBlocks));
- }
-
-
/**
* @param filteredUsageForInterval the list of invoiceItem to consider
* @return the price amount that was already billed for that period and usage section (across unitTypes)
@@ -471,10 +361,10 @@ public class ContiguousIntervalUsageInArrear {
return billedAmount;
}
- Iterable<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
+ List<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
Preconditions.checkState(isBuilt.get());
- return Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
+ final Iterable<InvoiceItem> filteredResult = Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
@Override
public boolean apply(final InvoiceItem input) {
if (input.getInvoiceItemType() != InvoiceItemType.USAGE) {
@@ -487,6 +377,7 @@ public class ContiguousIntervalUsageInArrear {
usageInput.getEndDate().compareTo(endDate) <= 0;
}
});
+ return ImmutableList.copyOf(filteredResult);
}
@VisibleForTesting
@@ -557,4 +448,14 @@ public class ContiguousIntervalUsageInArrear {
return nextNotificationDate;
}
}
+
+ protected String toJson(final Object usageInArrearAggregate) {
+ try {
+ return objectMapper.writeValueAsString(usageInArrearAggregate);
+ } catch (JsonProcessingException e) {
+ Preconditions.checkState(false, e.getMessage());
+ return null;
+ }
+ }
+
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearAggregate.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearAggregate.java
new file mode 100644
index 0000000..a90565c
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageCapacityInArrearAggregate.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageCapacityInArrearAggregate implements UsageInArrearAggregate {
+
+ private final List<UsageInArrearTierUnitDetail> tierDetails;
+ private BigDecimal amount;
+
+ @JsonCreator
+ public UsageCapacityInArrearAggregate(@JsonProperty("tierDetails") List<UsageInArrearTierUnitDetail> tierDetails,
+ @JsonProperty("amount") BigDecimal amount) {
+ this.tierDetails = tierDetails;
+ this.amount = amount;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public void setAmount(BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public List<UsageInArrearTierUnitDetail> getTierDetails() {
+ return tierDetails;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearAggregate.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearAggregate.java
new file mode 100644
index 0000000..77ec2c3
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearAggregate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageConsumableInArrearAggregate implements UsageInArrearAggregate {
+
+ private final List<UsageConsumableInArrearTierUnitAggregate> tierDetails;
+ private final BigDecimal amount;
+
+ public UsageConsumableInArrearAggregate(List<UsageConsumableInArrearTierUnitAggregate> tierDetails) {
+ this(tierDetails, computeAmount(tierDetails));
+ }
+
+ @JsonCreator
+ public UsageConsumableInArrearAggregate(@JsonProperty("tierDetails") List<UsageConsumableInArrearTierUnitAggregate> tierDetails,
+ @JsonProperty("amount") BigDecimal amount) {
+ this.tierDetails = tierDetails;
+ this.amount = amount;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public List<UsageConsumableInArrearTierUnitAggregate> getTierDetails() {
+ return tierDetails;
+ }
+
+ private static BigDecimal computeAmount(final List<UsageConsumableInArrearTierUnitAggregate> tierDetails) {
+ BigDecimal result = BigDecimal.ZERO;
+ for (UsageConsumableInArrearTierUnitAggregate toBeBilled : tierDetails) {
+ result = result.add(toBeBilled.getAmount());
+ }
+ return result;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java
new file mode 100644
index 0000000..0a423d8
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageConsumableInArrearTierUnitAggregate.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageConsumableInArrearTierUnitAggregate extends UsageInArrearTierUnitDetail {
+
+ private final int tierBlockSize;
+ private BigDecimal amount;
+
+ public UsageConsumableInArrearTierUnitAggregate(int tier, String tierUnit, BigDecimal tierPrice, Integer tierBlockSize, Integer quantity) {
+ this(tier, tierUnit, tierPrice, tierBlockSize, quantity, computeAmount(tierPrice, quantity));
+ }
+
+ @JsonCreator
+ public UsageConsumableInArrearTierUnitAggregate(@JsonProperty("tier") int tier,
+ @JsonProperty("tierUnit") String tierUnit,
+ @JsonProperty("tierPrice") BigDecimal tierPrice,
+ @JsonProperty("tierBlockSize") Integer tierBlockSize,
+ @JsonProperty("quantity") Integer quantity,
+ @JsonProperty("amount") BigDecimal amount) {
+ super(tier, tierUnit, tierPrice, quantity);
+ this.amount = amount;
+ this.tierBlockSize = tierBlockSize;
+ }
+
+ public int getTier() {
+ return tier;
+ }
+
+ public String getTierUnit() {
+ return tierUnit;
+ }
+
+ public BigDecimal getTierPrice() {
+ return tierPrice;
+ }
+
+ public Integer getQuantity() {
+ return quantity;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public void setAmount(BigDecimal amount) {
+ this.amount = amount;
+ }
+
+ public int getTierBlockSize() {
+ return tierBlockSize;
+ }
+
+ public void updateQuantityAndAmount(final Integer addionalQuantity) {
+ this.quantity = quantity + addionalQuantity;
+ this.amount = computeAmount(tierPrice, quantity);
+ }
+
+ private static BigDecimal computeAmount(final BigDecimal targetTierPrice, final Integer targetQuantity) {
+ return targetTierPrice.multiply(BigDecimal.valueOf(targetQuantity));
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearAggregate.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearAggregate.java
new file mode 100644
index 0000000..ea90086
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearAggregate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+public interface UsageInArrearAggregate {
+
+ BigDecimal getAmount();
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
new file mode 100644
index 0000000..59aebc7
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/details/UsageInArrearTierUnitDetail.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.usage.details;
+
+import java.math.BigDecimal;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageInArrearTierUnitDetail {
+
+ protected final int tier;
+ protected final String tierUnit;
+ protected final BigDecimal tierPrice;
+ protected Integer quantity;
+
+ @JsonCreator
+ public UsageInArrearTierUnitDetail(@JsonProperty("tier") int tier,
+ @JsonProperty("tierUnit") String tierUnit,
+ @JsonProperty("tierPrice") BigDecimal tierPrice,
+ @JsonProperty("quantity") Integer quantity) {
+ this.tier = tier;
+ this.tierUnit = tierUnit;
+ this.tierPrice = tierPrice;
+ this.quantity = quantity;
+ }
+
+ public int getTier() {
+ return tier;
+ }
+
+ public String getTierUnit() {
+ return tierUnit;
+ }
+
+ public BigDecimal getTierPrice() {
+ return tierPrice;
+ }
+
+ public Integer getQuantity() {
+ return quantity;
+ }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
index 295c376..b0f27f8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
@@ -134,7 +134,7 @@ public class RawUsageOptimizer {
}
}
- final LocalDate result = targetStartDate.compareTo(firstEventStartDate) > 0 ? targetStartDate : firstEventStartDate;
+ final LocalDate result = targetStartDate != null && targetStartDate.compareTo(firstEventStartDate) > 0 ? targetStartDate : firstEventStartDate;
return result;
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
index 726706c..93bbd42 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/SubscriptionUsageInArrear.java
@@ -33,11 +33,14 @@ import org.killbill.billing.catalog.api.BillingMode;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.generator.InvoiceItemGenerator.InvoiceItemGeneratorLogger;
import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
@@ -78,6 +81,7 @@ public class SubscriptionUsageInArrear {
private final List<RawUsage> rawSubscriptionUsage;
private final LocalDate rawUsageStartDate;
private final InternalTenantContext internalTenantContext;
+ private final UsageDetailMode usageDetailMode;
public SubscriptionUsageInArrear(final UUID accountId,
final UUID invoiceId,
@@ -85,6 +89,7 @@ public class SubscriptionUsageInArrear {
final List<RawUsage> rawUsage,
final LocalDate targetDate,
final LocalDate rawUsageStartDate,
+ final UsageDetailMode usageDetailMode,
final InternalTenantContext internalTenantContext) {
this.accountId = accountId;
@@ -100,6 +105,7 @@ public class SubscriptionUsageInArrear {
return input.getSubscriptionId().equals(subscriptionBillingEvents.get(0).getSubscription().getId());
}
}));
+ this.usageDetailMode = usageDetailMode;
}
/**
@@ -108,16 +114,16 @@ public class SubscriptionUsageInArrear {
* @param existingUsage the existing on disk usage items.
* @throws CatalogApiException
*/
- public SubscriptionUsageInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger) throws CatalogApiException {
+ public SubscriptionUsageInArrearItemsAndNextNotificationDate computeMissingUsageInvoiceItems(final List<InvoiceItem> existingUsage, final InvoiceItemGeneratorLogger invoiceItemGeneratorLogger) throws CatalogApiException, InvoiceApiException {
final SubscriptionUsageInArrearItemsAndNextNotificationDate result = new SubscriptionUsageInArrearItemsAndNextNotificationDate();
final List<ContiguousIntervalUsageInArrear> billingEventTransitionTimePeriods = computeInArrearUsageInterval();
for (final ContiguousIntervalUsageInArrear usageInterval : billingEventTransitionTimePeriods) {
- final UsageInArrearItemsAndNextNotificationDate newItemsAndDate = usageInterval.computeMissingItemsAndNextNotificationDate(existingUsage);
+ final UsageInArrearItemsAndNextNotificationDate newItemsWithDetailsAndDate = usageInterval.computeMissingItemsAndNextNotificationDate(existingUsage);
// For debugging purposes
- invoiceItemGeneratorLogger.append(usageInterval, newItemsAndDate.getInvoiceItems());
+ invoiceItemGeneratorLogger.append(usageInterval, newItemsWithDetailsAndDate.getInvoiceItems());
- result.addUsageInArrearItemsAndNextNotificationDate(usageInterval.getUsage().getName(), newItemsAndDate);
+ result.addUsageInArrearItemsAndNextNotificationDate(usageInterval.getUsage().getName(), newItemsWithDetailsAndDate);
}
return result;
}
@@ -149,7 +155,10 @@ public class SubscriptionUsageInArrear {
// Add inflight usage interval if non existent
ContiguousIntervalUsageInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
if (existingInterval == null) {
- existingInterval = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, internalTenantContext);
+ existingInterval = usage.getUsageType() == UsageType.CAPACITY ?
+ new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext) :
+ new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+
inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
}
// Add billing event for that usage interval
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
index 0a39859..5e0e026 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
@@ -38,13 +38,19 @@ public class UsageUtils {
Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CONSUMABLE);
Preconditions.checkArgument(usage.getTiers().length > 0);
+
final List<TieredBlock> result = Lists.newLinkedList();
for (Tier tier : usage.getTiers()) {
+ boolean found = false;
for (TieredBlock tierBlock : tier.getTieredBlocks()) {
if (tierBlock.getUnit().getName().equals(unitType)) {
result.add(tierBlock);
+ found = true;
+ break;
}
}
+ // We expect this method to return an ordered list of TieredBlock, each for each tier.
+ Preconditions.checkState(found, "Catalog issue in usage section '%s': Missing tierBlock definition for unit '%s'", usage.getName(), unitType);
}
return result;
}
@@ -63,7 +69,6 @@ public class UsageUtils {
return result;
}
-
public static List<Tier> getCapacityInArrearTier(final Usage usage) {
Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CAPACITY);
@@ -71,7 +76,6 @@ public class UsageUtils {
return ImmutableList.copyOf(usage.getTiers());
}
-
public static Set<String> getCapacityInArrearUnitTypes(final Usage usage) {
Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CAPACITY);
@@ -86,5 +90,4 @@ public class UsageUtils {
return result;
}
-
}
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index c7596b4..eccd1c5 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -19,6 +19,8 @@ tableFields(prefix) ::= <<
, <prefix>rate
, <prefix>currency
, <prefix>linked_item_id
+, <prefix>quantity
+, <prefix>item_details
, <prefix>created_by
, <prefix>created_date
>>
@@ -40,6 +42,8 @@ tableValues() ::= <<
, :rate
, :currency
, :linkedItemId
+, :quantity
+, :itemDetails
, :createdBy
, :createdDate
>>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
index 52f4ee7..f2bce36 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -20,6 +20,8 @@ CREATE TABLE invoice_items (
rate numeric(15,9) NULL,
currency varchar(3) NOT NULL,
linked_item_id varchar(36),
+ quantity int,
+ item_details text,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
account_record_id bigint /*! unsigned */ not null,
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180123114605__invoice_item_quantity_item_details.sql b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180123114605__invoice_item_quantity_item_details.sql
new file mode 100644
index 0000000..a78535d
--- /dev/null
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20180123114605__invoice_item_quantity_item_details.sql
@@ -0,0 +1,2 @@
+alter table invoice_items add column quantity int after linked_item_id;
+alter table invoice_items add column item_details text after quantity;
\ No newline at end of file
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 4447c42..a369fce 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -90,11 +90,11 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceTestSuiteWithEmbedded
@Test(groups = "slow")
public void testUserApiAccess() {
- final List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId, false, callContext);
+ final List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId, false, false, callContext);
Assert.assertEquals(byAccount.size(), 1);
Assert.assertEquals(byAccount.get(0).getId(), regularInvoiceId);
- final List<Invoice> byAccountAndDate = invoiceUserApi.getInvoicesByAccount(accountId, date_migrated.minusDays(1), callContext);
+ final List<Invoice> byAccountAndDate = invoiceUserApi.getInvoicesByAccount(accountId, date_migrated.minusDays(1), false, callContext);
Assert.assertEquals(byAccountAndDate.size(), 1);
Assert.assertEquals(byAccountAndDate.get(0).getId(), regularInvoiceId);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 52b0e08..3d5e624 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,19 +24,25 @@ import java.util.UUID;
import javax.annotation.Nullable;
+import org.joda.time.DateTime;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.callcontext.DefaultCallContext;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.killbill.billing.invoice.TestInvoiceHelper.DryRunFutureDateArguments;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
import org.killbill.billing.invoice.api.InvoiceStatus;
import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.invoice.model.DefaultInvoicePayment;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.callcontext.CallContext;
@@ -73,7 +79,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, "description", clock.getUTCToday(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, "description", clock.getUTCToday(), clock.getUTCToday(), externalChargeAmount, accountCurrency, null);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
verifyExternalChargeOnNewInvoice(accountBalance, null, externalChargeAmount, externalChargeInvoiceItem);
@@ -88,7 +94,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
final UUID bundleId = UUID.randomUUID();
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), clock.getUTCToday(),externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), clock.getUTCToday(),externalChargeAmount, accountCurrency, null);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
verifyExternalChargeOnNewInvoice(accountBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
}
@@ -119,7 +125,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), null, externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), null, externalChargeAmount, accountCurrency, null);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
final Invoice newInvoice = invoiceUserApi.getInvoice(externalChargeInvoiceItem.getInvoiceId(), callContext);
@@ -134,7 +140,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Post an external charge
final BigDecimal externalChargeAmount = BigDecimal.TEN;
final UUID bundleId = UUID.randomUUID();
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), null, externalChargeAmount, accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), null, externalChargeAmount, accountCurrency, null);
final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), true, callContext).get(0);
Assert.assertEquals(externalChargeInvoiceItem.getBundleId(), bundleId);
}
@@ -236,7 +242,22 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
}
@Test(groups = "slow")
- public void testAdjustPartialInvoiceItem() throws Exception {
+ public void testAdjustPartialRecurringInvoiceItem() throws Exception {
+ testAdjustPartialInvoiceItem(true);
+ }
+
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/pull/831")
+ public void testAdjustPartialFixedInvoiceItem() throws Exception {
+ testAdjustPartialInvoiceItem(false);
+ }
+
+ private void testAdjustPartialInvoiceItem(final boolean recurring) throws Exception {
+ final Account account = invoiceUtil.createAccount(callContext);
+ final UUID accountId = account.getId();
+ final BigDecimal fixedPrice = recurring ? null : BigDecimal.ONE;
+ final BigDecimal recurringPrice = !recurring ? null : BigDecimal.ONE;
+ final UUID invoiceId = invoiceUtil.generateRegularInvoice(account, fixedPrice, recurringPrice, null, callContext);
+
final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId, callContext).getInvoiceItems().get(0);
// Verify we picked a non zero item
Assert.assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
@@ -268,6 +289,10 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
// Verify the adjusted account balance
final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+
+ // Verify future invoice generation
+ invoiceUtil.generateInvoice(account.getId(), null, new DryRunFutureDateArguments(), internalCallContext);
+ // Invoice may or may not be generated, but there is no exception
}
@Test(groups = "slow")
@@ -342,7 +367,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertEquals(creditInvoice.getStatus(), InvoiceStatus.COMMITTED);
try {
- final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, "Initial external charge", clock.getUTCToday(), null, new BigDecimal("12.33"), accountCurrency);
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, "Initial external charge", clock.getUTCToday(), null, new BigDecimal("12.33"), accountCurrency, null);
invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.of(externalCharge), true, callContext);
Assert.fail("Should fail to add external charge on already committed invoice");
} catch (final InvoiceApiException e) {
@@ -357,4 +382,38 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ALREADY_COMMITTED.getCode());
}
}
+
+ @Test(groups = "slow")
+ public void testVoidInvoice() throws Exception {
+ // try to void invoice
+ invoiceUserApi.voidInvoice(invoiceId, callContext);
+
+ final Invoice invoice = invoiceUserApi.getInvoice(invoiceId, callContext);
+ Assert.assertEquals(invoice.getStatus(), InvoiceStatus.VOID);
+ }
+
+ @Test(groups = "slow")
+ public void testVoidInvoiceThatIsPaid() throws Exception {
+ InternalCallContext context = internalCallContextFactory.createInternalCallContext(accountId, callContext);
+ // Verify the initial invoice balance
+ final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId, callContext).getBalance();
+ Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId, callContext);
+ Assert.assertEquals(accountBalance, invoiceBalance);
+
+ // create payment
+ final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoiceId, new DateTime(), invoiceBalance, Currency.USD, Currency.USD, null, true);
+ invoiceUtil.createPayment(payment, context);
+
+ // try to void invoice, it should fail
+ try {
+ invoiceUserApi.voidInvoice(invoiceId, callContext);
+ Assert.fail("Should fail to void invoice that is already paid");
+ } catch (final InvoiceApiException e) {
+ Assert.assertEquals(e.getCode(), ErrorCode.CAN_NOT_VOID_INVOICE_THAT_IS_PAID.getCode());
+ }
+
+ }
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
index 2810693..63b366c 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestInvoiceFlagBehaviors.java
@@ -53,7 +53,7 @@ public class TestInvoiceFlagBehaviors extends InvoiceTestSuiteWithEmbeddedDB {
public void testWrittenOffInvoiceBeforeAccountCredit() throws Exception {
// Create new invoice with one charge and expect account credit to be used
- final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, BigDecimal.TEN, accountCurrency)), true, callContext);
+ final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, BigDecimal.TEN, accountCurrency, null)), true, callContext);
assertEquals(items.size(), 1);
// Check both invoice and account balance is 10.00
@@ -109,7 +109,7 @@ public class TestInvoiceFlagBehaviors extends InvoiceTestSuiteWithEmbeddedDB {
assertEquals(accountCBA0.compareTo(BigDecimal.TEN), 0);
// Create new invoice with one charge and expect account credit to be used
- final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, new BigDecimal("13.5"), accountCurrency)), true, callContext);
+ final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, new BigDecimal("13.5"), accountCurrency, null)), true, callContext);
assertEquals(items.size(), 1);
final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(accountId, callContext);
@@ -156,7 +156,7 @@ public class TestInvoiceFlagBehaviors extends InvoiceTestSuiteWithEmbeddedDB {
invoiceUserApi.insertCredit(accountId, BigDecimal.TEN, null, accountCurrency, true, null, callContext);
// Create new invoice with one charge and expect account credit to be used
- final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, new BigDecimal("4.0"), accountCurrency)), false, callContext);
+ final List<InvoiceItem> items = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(new ExternalChargeInvoiceItem(UUID.randomUUID(), clock.getUTCNow(), null, accountId, null, null, null, null, new BigDecimal("4.0"), accountCurrency, null)), false, callContext);
assertEquals(items.size(), 1);
final UUID invoiceId = items.get(0).getInvoiceId();
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index 0e1ea27..4ce8e42 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -134,7 +134,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
@Override
- public List<InvoiceModelDao> getInvoicesByAccount(final InternalTenantContext context) {
+ public List<InvoiceModelDao> getInvoicesByAccount(final Boolean includeVoidedInvoices, final InternalTenantContext context) {
final List<InvoiceModelDao> result = new ArrayList<InvoiceModelDao>();
synchronized (monitor) {
@@ -149,12 +149,13 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
@Override
- public List<InvoiceModelDao> getInvoicesByAccount(final LocalDate fromDate, final InternalTenantContext context) {
+ public List<InvoiceModelDao> getInvoicesByAccount(final Boolean includeVoidedInvoices, final LocalDate fromDate, final InternalTenantContext context) {
final List<InvoiceModelDao> invoicesForAccount = new ArrayList<InvoiceModelDao>();
synchronized (monitor) {
final UUID accountId = accountRecordIds.inverse().get(context.getAccountRecordId());
for (final InvoiceModelDao invoice : getAll(context)) {
- if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate) && !invoice.isMigrated()) {
+ if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate) && !invoice.isMigrated() &&
+ (includeVoidedInvoices ? true : !InvoiceStatus.VOID.equals(invoice.getStatus()))) {
invoicesForAccount.add(invoice);
}
}
@@ -281,13 +282,13 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
@Override
- public List<InvoiceModelDao> getAllInvoicesByAccount(final InternalTenantContext context) {
+ public List<InvoiceModelDao> getAllInvoicesByAccount(final Boolean includeVoidedInvoices, final InternalTenantContext context) {
final List<InvoiceModelDao> result = new ArrayList<InvoiceModelDao>();
synchronized (monitor) {
final UUID accountId = accountRecordIds.inverse().get(context.getAccountRecordId());
for (final InvoiceModelDao invoice : invoices.values()) {
- if (accountId.equals(invoice.getAccountId())) {
+ if (accountId.equals(invoice.getAccountId()) && (includeVoidedInvoices ? true : !InvoiceStatus.VOID.equals(invoice.getStatus()))) {
result.add(invoice);
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 73737e0..9e2e1a8 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -119,7 +119,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
invoiceUtil.createInvoice(invoice, context);
- final List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(context);
+ final List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(false, context);
assertNotNull(invoices);
assertEquals(invoices.size(), 1);
final InvoiceModelDao thisInvoice = invoices.get(0);
@@ -485,19 +485,19 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
invoiceUtil.createInvoice(invoice2, context);
List<InvoiceModelDao> invoices;
- invoices = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 1, 1), context);
+ invoices = invoiceDao.getInvoicesByAccount(false, new LocalDate(2011, 1, 1), context);
assertEquals(invoices.size(), 2);
- invoices = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 10, 6), context);
+ invoices = invoiceDao.getInvoicesByAccount(false, new LocalDate(2011, 10, 6), context);
assertEquals(invoices.size(), 2);
- invoices = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 10, 11), context);
+ invoices = invoiceDao.getInvoicesByAccount(false, new LocalDate(2011, 10, 11), context);
assertEquals(invoices.size(), 1);
- invoices = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 12, 6), context);
+ invoices = invoiceDao.getInvoicesByAccount(false, new LocalDate(2011, 12, 6), context);
assertEquals(invoices.size(), 1);
- invoices = invoiceDao.getInvoicesByAccount(new LocalDate(2012, 1, 1), context);
+ invoices = invoiceDao.getInvoicesByAccount(false, new LocalDate(2012, 1, 1), context);
assertEquals(invoices.size(), 0);
}
@@ -818,7 +818,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final String description = UUID.randomUUID().toString();
final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false);
- final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+ final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD, null));
invoiceForExternalCharge.addInvoiceItem(externalCharge);
final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), context).get(0);
@@ -855,7 +855,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
final String description = UUID.randomUUID().toString();
final InvoiceModelDao draftInvoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD, false, InvoiceStatus.DRAFT);
- final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(draftInvoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+ final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(draftInvoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD, null));
draftInvoiceForExternalCharge.addInvoiceItem(externalCharge);
final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftInvoiceForExternalCharge), context).get(0);
@@ -980,7 +980,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
createCredit(accountId, effectiveDate, creditAmount, true);
- final List<InvoiceModelDao> invoices = invoiceDao.getAllInvoicesByAccount(context);
+ final List<InvoiceModelDao> invoices = invoiceDao.getAllInvoicesByAccount(false, context);
assertEquals(invoices.size(), 1);
final InvoiceModelDao invoice = invoices.get(0);
@@ -1055,7 +1055,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
createCredit(accountId, invoice1.getId(), effectiveDate, creditAmount, false);
- final List<InvoiceModelDao> invoices = invoiceDao.getAllInvoicesByAccount(context);
+ final List<InvoiceModelDao> invoices = invoiceDao.getAllInvoicesByAccount(false, context);
assertEquals(invoices.size(), 1);
final InvoiceModelDao invoice = invoices.get(0);
@@ -1164,13 +1164,13 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate, context);
assertEquals(invoices.size(), 1);
- List<InvoiceModelDao> allInvoicesByAccount = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 1, 1), context);
+ List<InvoiceModelDao> allInvoicesByAccount = invoiceDao.getInvoicesByAccount(false, new LocalDate(2011, 1, 1), context);
assertEquals(allInvoicesByAccount.size(), 1);
// insert DRAFT invoice
createCredit(accountId, new LocalDate(2011, 12, 31), BigDecimal.TEN, true);
- allInvoicesByAccount = invoiceDao.getInvoicesByAccount(new LocalDate(2011, 1, 1), context);
+ allInvoicesByAccount = invoiceDao.getInvoicesByAccount(false, new LocalDate(2011, 1, 1), context);
assertEquals(allInvoicesByAccount.size(), 2);
assertEquals(allInvoicesByAccount.get(0).getStatus(), InvoiceStatus.COMMITTED);
assertEquals(allInvoicesByAccount.get(1).getStatus(), InvoiceStatus.DRAFT);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
index 61836c7..14db61d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceItemDao.java
@@ -187,7 +187,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
final LocalDate startDate = new LocalDate(2012, 4, 1);
final LocalDate endDate = new LocalDate(2012, 5, 1);
final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(invoiceId, accountId, bundleId, description,
- startDate, endDate, TEN, Currency.USD);
+ startDate, endDate, TEN, Currency.USD, null);
invoiceUtil.createInvoiceItem(externalChargeInvoiceItem, context);
final InvoiceItemModelDao savedItem = invoiceUtil.getInvoiceItemById(externalChargeInvoiceItem.getId(), context);
@@ -223,7 +223,7 @@ public class TestInvoiceItemDao extends InvoiceTestSuiteWithEmbeddedDB {
private void createAndVerifyExternalCharge(final BigDecimal amount, final Currency currency) throws EntityPersistenceException {
final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(UUID.randomUUID(), account.getId(), UUID.randomUUID(),
- UUID.randomUUID().toString(), new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), amount, currency);
+ UUID.randomUUID().toString(), new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), amount, currency, null);
invoiceUtil.createInvoiceItem(externalChargeInvoiceItem, context);
final InvoiceItemModelDao savedItem = invoiceUtil.getInvoiceItemById(externalChargeInvoiceItem.getId(), context);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
index dcd4a44..dff38c1 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
@@ -39,6 +39,8 @@ import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.util.api.TagUserApi;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.definition.InvoiceConfig;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
import org.killbill.commons.locker.GlobalLocker;
@@ -67,6 +69,8 @@ public abstract class InvoiceTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
@Inject
protected InvoiceGenerator generator;
@Inject
+ protected InvoiceConfig invoiceConfig;
+ @Inject
protected BillingInternalApi billingApi;
@Inject
protected AccountInternalApi accountApi;
@@ -104,6 +108,7 @@ public abstract class InvoiceTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
protected KillbillConfigSource getConfigSource() {
return getConfigSource("/resource.properties");
}
+ protected UsageDetailMode usageDetailMode;
@BeforeClass(groups = "fast")
protected void beforeClass() throws Exception {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
index 3bcf72f..2e70465 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -41,7 +41,7 @@ public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
final BigDecimal amount = BigDecimal.TEN;
final Currency currency = Currency.GBP;
final ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
- effectiveDate, effectiveDate, amount, currency);
+ effectiveDate, effectiveDate, amount, currency, null);
Assert.assertEquals(item.getAccountId(), accountId);
Assert.assertEquals(item.getAmount().compareTo(amount), 0);
Assert.assertEquals(item.getBundleId(), bundleId);
@@ -55,19 +55,20 @@ public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
Assert.assertNull(item.getPhaseName());
Assert.assertNull(item.getRate());
Assert.assertNull(item.getSubscriptionId());
+ Assert.assertNull(item.getItemDetails());
Assert.assertEquals(item, item);
final ExternalChargeInvoiceItem otherItem = new ExternalChargeInvoiceItem(id, invoiceId, UUID.randomUUID(), bundleId,
- description, effectiveDate, effectiveDate, amount, currency);
+ description, effectiveDate, effectiveDate, amount, currency, null);
Assert.assertNotEquals(otherItem, item);
// Check comparison (done by start date)
final ExternalChargeInvoiceItem itemBefore = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
- effectiveDate.minusDays(1), effectiveDate.minusDays(1), amount, currency);
+ effectiveDate.minusDays(1), effectiveDate.minusDays(1), amount, currency, null);
Assert.assertFalse(itemBefore.matches(item));
final ExternalChargeInvoiceItem itemAfter = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
- effectiveDate.plusDays(1), effectiveDate.minusDays(1), amount, currency);
+ effectiveDate.plusDays(1), effectiveDate.minusDays(1), amount, currency, null);
Assert.assertFalse(itemAfter.matches(item));
}
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index a07977a..ea9001c 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -107,21 +107,21 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice);
- List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(context);
+ List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 0);
// Try it again to double check
invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice);
- invoices = invoiceDao.getInvoicesByAccount(context);
+ invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 0);
// This time no dry run
invoice = dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, null, context);
Assert.assertNotNull(invoice);
- invoices = invoiceDao.getInvoicesByAccount(context);
+ invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 1);
}
@@ -201,7 +201,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertTrue(e.getCause().getMessage().startsWith("Double billing detected"));
}
// Dry-run: no side effect on disk
- Assert.assertEquals(invoiceDao.getInvoicesByAccount(context).size(), 1);
+ Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
Assert.assertTrue(tagUserApi.getTagsForAccount(accountId, true, callContext).isEmpty());
try {
@@ -211,7 +211,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
Assert.assertTrue(e.getCause().getMessage().startsWith("Double billing detected"));
}
- Assert.assertEquals(invoiceDao.getInvoicesByAccount(context).size(), 1);
+ Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
// No dry-run: account is parked
final List<Tag> tags = tagUserApi.getTagsForAccount(accountId, false, callContext);
Assert.assertEquals(tags.size(), 1);
@@ -230,7 +230,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
Assert.assertTrue(e.getCause().getMessage().startsWith("Double billing detected"));
}
// Idempotency
- Assert.assertEquals(invoiceDao.getInvoicesByAccount(context).size(), 1);
+ Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext), tags);
// Fix state
@@ -250,14 +250,14 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
// Dry-run and isApiCall=true: call goes through
final Invoice invoice1 = dispatcher.processAccount(true, accountId, target, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice1);
- Assert.assertEquals(invoiceDao.getInvoicesByAccount(context).size(), 0);
+ Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 0);
// Dry-run: still parked
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext).size(), 1);
// No dry-run and isApiCall=true: call goes through
final Invoice invoice2 = dispatcher.processAccount(true, accountId, target, null, context);
Assert.assertNotNull(invoice2);
- Assert.assertEquals(invoiceDao.getInvoicesByAccount(context).size(), 1);
+ Assert.assertEquals(invoiceDao.getInvoicesByAccount(false, context).size(), 1);
// No dry-run: now unparked
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, false, callContext).size(), 0);
Assert.assertEquals(tagUserApi.getTagsForAccount(accountId, true, callContext).size(), 1);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index b1846ee..f5e9bd1 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -76,7 +76,6 @@ import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
-import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.billing.util.config.definition.InvoiceConfig;
@@ -197,6 +196,12 @@ public class TestInvoiceHelper {
}
public UUID generateRegularInvoice(final Account account, final LocalDate targetDate, final CallContext callContext) throws Exception {
+ final BigDecimal fixedPrice = null;
+ final BigDecimal recurringPrice = BigDecimal.ONE;
+ return generateRegularInvoice(account, fixedPrice, recurringPrice, targetDate, callContext);
+ }
+
+ public UUID generateRegularInvoice(final Account account, final BigDecimal fixedPrice, final BigDecimal recurringPrice, final LocalDate targetDate, final CallContext callContext) throws Exception {
final SubscriptionBase subscription = Mockito.mock(SubscriptionBase.class);
Mockito.when(subscription.getId()).thenReturn(UUID.randomUUID());
Mockito.when(subscription.getBundleId()).thenReturn(new UUID(0L, 0L));
@@ -205,34 +210,37 @@ public class TestInvoiceHelper {
final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
final DateTime effectiveDate = new DateTime().minusDays(1);
final Currency currency = Currency.USD;
- final BigDecimal fixedPrice = null;
events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
- fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+ fixedPrice, recurringPrice, currency, BillingPeriod.MONTHLY, 1,
BillingMode.IN_ADVANCE, "", 1L, SubscriptionBaseTransitionType.CREATE));
Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<DryRunArguments>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
- final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi,
- invoiceDao, internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
- notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
+ final InternalCallContext context = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
- Invoice invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), targetDate, new DryRunFutureDateArguments(), internalCallContext);
+ Invoice invoice = generateInvoice(account.getId(), targetDate, new DryRunFutureDateArguments(), context);
Assert.assertNotNull(invoice);
- final InternalCallContext context = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-
- List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(context);
+ List<InvoiceModelDao> invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 0);
- invoice = dispatcher.processAccountFromNotificationOrBusEvent(account.getId(), targetDate, null, context);
+ invoice = generateInvoice(account.getId(), targetDate, null, context);
Assert.assertNotNull(invoice);
- invoices = invoiceDao.getInvoicesByAccount(context);
+ invoices = invoiceDao.getInvoicesByAccount(false, context);
Assert.assertEquals(invoices.size(), 1);
return invoice.getId();
}
+ public Invoice generateInvoice(final UUID accountId, @Nullable final LocalDate targetDate, @Nullable final DryRunArguments dryRunArguments, final InternalCallContext internalCallContext) throws InvoiceApiException {
+ final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi,
+ invoiceDao, internalCallContextFactory, invoicePluginDispatcher, locker, busService.getBus(),
+ notificationQueueService, invoiceConfig, clock, parkedAccountsManager);
+
+ return dispatcher.processAccountFromNotificationOrBusEvent(accountId, targetDate, dryRunArguments, internalCallContext);
+ }
+
public SubscriptionBase createSubscription() throws SubscriptionBaseApiException {
final UUID uuid = UUID.randomUUID();
final UUID bundleId = UUID.randomUUID();
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
index f64b95e..043d29b 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java
@@ -17,6 +17,7 @@
package org.killbill.billing.invoice.usage;
+import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
@@ -26,33 +27,33 @@ import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.catalog.DefaultLimit;
import org.killbill.billing.catalog.DefaultTier;
-import org.killbill.billing.catalog.DefaultTieredBlock;
import org.killbill.billing.catalog.DefaultUnit;
import org.killbill.billing.catalog.DefaultUsage;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
+import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearAggregate;
+import org.killbill.billing.invoice.usage.details.UsageInArrearTierUnitDetail;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUnit;
-import org.killbill.billing.usage.api.RolledUpUsage;
import org.killbill.billing.usage.api.svcs.DefaultRawUsage;
-import org.testng.Assert;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.google.common.base.Function;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@@ -84,11 +85,11 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final LocalDate targetDate = startDate.plusDays(1);
- final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
final List<InvoiceItem> existingUsage = Lists.newArrayList();
final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
@@ -116,88 +117,103 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
@Test(groups = "fast")
public void testComputeBilledUsage() throws CatalogApiException {
-
final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
+ final DefaultUnit unit3 = new DefaultUnit().setName("unit3");
final DefaultLimit limit1_1 = new DefaultLimit().setUnit(unit1).setMax((double) 100).setMin((double) -1);
final DefaultLimit limit1_2 = new DefaultLimit().setUnit(unit2).setMax((double) 1000).setMin((double) -1);
- final DefaultTier tier1 = createDefaultTierWithLimits(BigDecimal.TEN, limit1_1, limit1_2);
+ final DefaultLimit limit1_3 = new DefaultLimit().setUnit(unit3).setMax((double) 50).setMin((double) -1);
+ final DefaultTier tier1 = createDefaultTierWithLimits(BigDecimal.TEN, limit1_1, limit1_2, limit1_3);
final DefaultLimit limit2_1 = new DefaultLimit().setUnit(unit1).setMax((double) 200).setMin((double) -1);
final DefaultLimit limit2_2 = new DefaultLimit().setUnit(unit2).setMax((double) 2000).setMin((double) -1);
- final DefaultTier tier2 = createDefaultTierWithLimits(new BigDecimal("20.0"), limit2_1, limit2_2);
+ final DefaultLimit limit2_3 = new DefaultLimit().setUnit(unit3).setMax((double) 100).setMin((double) -1);
+ final DefaultTier tier2 = createDefaultTierWithLimits(new BigDecimal("20.0"), limit2_1, limit2_2, limit2_3);
// Don't define any max for last tier to allow any number
final DefaultLimit limit3_1 = new DefaultLimit().setUnit(unit1).setMin((double) -1).setMax((double) -1);
final DefaultLimit limit3_2 = new DefaultLimit().setUnit(unit2).setMin((double) -1).setMax((double) -1);
- final DefaultTier tier3 = createDefaultTierWithLimits(new BigDecimal("30.0"), limit3_1, limit3_2);
-
+ final DefaultLimit limit3_3 = new DefaultLimit().setUnit(unit3).setMax((double) -1).setMin((double) -1);
+ final DefaultTier tier3 = createDefaultTierWithLimits(new BigDecimal("30.0"), limit3_1, limit3_2, limit3_3);
final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier1, tier2, tier3);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalCapacityInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
// Tier 1 (both units from tier 1)
- BigDecimal result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
- new DefaultRolledUpUnit("unit2", 1000L)));
- assertEquals(result, BigDecimal.TEN);
+ UsageCapacityInArrearAggregate result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
+ new DefaultRolledUpUnit("unit2", 1000L),
+ new DefaultRolledUpUnit("unit3", 50L)));
+ assertEquals(result.getTierDetails().size(), 3);
+ assertTrue(result.getAmount().compareTo(BigDecimal.TEN) == 0);
// Tier 2 (only one unit from tier 1)
result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
new DefaultRolledUpUnit("unit2", 1001L)));
- assertEquals(result, new BigDecimal("20.0"));
+ assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
// Tier 2 (only one unit from tier 1)
result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 101L),
new DefaultRolledUpUnit("unit2", 1000L)));
- assertEquals(result, new BigDecimal("20.0"));
-
+ assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
// Tier 2 (both units from tier 2)
result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 101L),
new DefaultRolledUpUnit("unit2", 1001L)));
- assertEquals(result, new BigDecimal("20.0"));
+ assertTrue(result.getAmount().compareTo(new BigDecimal("20.0")) == 0);
// Tier 3 (only one unit from tier 3)
result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 10L),
new DefaultRolledUpUnit("unit2", 2001L)));
- assertEquals(result, new BigDecimal("30.0"));
+ assertTrue(result.getAmount().compareTo(new BigDecimal("30.0")) == 0);
}
@Test(groups = "fast")
- public void testComputeMissingItems() throws CatalogApiException {
+ public void testComputeMissingItems() throws CatalogApiException, InvoiceApiException {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
final LocalDate endDate = new LocalDate(2014, 05, 15);
- // 2 items for startDate - firstBCDDate
final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit", 130L));
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit", 271L));
- // 1 items for firstBCDDate - endDate
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit", 199L));
+ //
+ // First period: startDate - firstBCDDate
+ //
+ // 2 items for unit1
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit1", 130L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit1", 271L));
+ // 1 items for unit2
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 24), "unit2", 10L));
+
+ //
+ // Second period: firstBCDDate - endDate
+ //
+ // 1 items unit1
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit1", 199L));
+ // 1 items unit2
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit2", 20L));
- final DefaultUnit unit = new DefaultUnit().setName("unit");
- final DefaultLimit limit = new DefaultLimit().setUnit(unit).setMax((double) -1);
+ final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
+ final DefaultLimit limit1 = new DefaultLimit().setUnit(unit1).setMax((double) -1);
- final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit);
+ final DefaultUnit unit2 = new DefaultUnit().setName("unit2");
+ final DefaultLimit limit2 = new DefaultLimit().setUnit(unit2).setMax((double) -1);
- final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
+ final DefaultTier tier = createDefaultTierWithLimits(BigDecimal.TEN, limit1, limit2);
+ final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier);
final LocalDate targetDate = endDate;
-
- final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2);
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, event1, event2);
final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
@@ -206,7 +222,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
invoiceItems.add(ii2);
- final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
+ final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
assertEquals(rawResults.size(), 4);
@@ -217,7 +233,6 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
}
}));
-
assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9.0")), 0, String.format("%s != 9.0", result.get(0).getAmount()));
assertEquals(result.get(0).getCurrency(), Currency.BTC);
assertEquals(result.get(0).getAccountId(), accountId);
@@ -241,6 +256,189 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
assertTrue(result.get(1).getEndDate().compareTo(endDate) == 0);
}
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersAggregateMode() throws CatalogApiException, IOException, InvoiceApiException {
+ testMultipleItemsAndTiers(UsageDetailMode.AGGREGATE);
+ }
+
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersDetailMode() throws CatalogApiException, IOException, InvoiceApiException {
+ testMultipleItemsAndTiers(UsageDetailMode.DETAIL);
+ }
+
+ private void testMultipleItemsAndTiers(UsageDetailMode usageDetailMode) throws CatalogApiException, IOException, InvoiceApiException {
+
+ // Case 1
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(BigDecimal.ONE), 0, String.format("%s != 1.0", result.get(0).getAmount()));
+
+ UsageCapacityInArrearAggregate itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearAggregate>() {});
+ assertEquals(itemDetails.getAmount().compareTo(BigDecimal.ONE), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+
+ List<UsageInArrearTierUnitDetail> itemUnitDetails = itemDetails.getTierDetails();
+ // BAR item detail
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(0).getTier(), 1);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 99);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
+ // FOO item detail
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(1).getTier(), 1);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 5);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // Case 2
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+ result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(BigDecimal.TEN), 0, String.format("%s != 10.0", result.get(0).getAmount()));
+
+ itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearAggregate>() {});
+ assertEquals(itemDetails.getAmount().compareTo(BigDecimal.TEN), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+ itemUnitDetails = itemDetails.getTierDetails();
+
+ // FOO item detail
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(0).getTier(), 1);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 5);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // BAR item detail
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(1).getTier(), 2);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 101);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(BigDecimal.TEN), 0);
+
+ // Case 3
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+ result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("100.0")), 0, String.format("%s != 100.0", result.get(0).getAmount()));
+
+ itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearAggregate>() {});
+ assertEquals(itemDetails.getAmount().compareTo(new BigDecimal("100.0")), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+ itemUnitDetails = itemDetails.getTierDetails();
+
+ // BAR item detail
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(0).getTier(), 2);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 101);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(new BigDecimal("10.0")), 0);
+
+ // FOO item detail
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(1).getTier(), 3);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 75);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+
+ }
+
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersWithExistingItems() throws CatalogApiException, IOException, InvoiceApiException {
+
+ // let's assume we have some existing usage
+ final UsageInArrearTierUnitDetail existingFooUsageTier1 = new UsageInArrearTierUnitDetail(1, "FOO", BigDecimal.ONE, 9);
+ final UsageInArrearTierUnitDetail existingBarUsageTier2 = new UsageInArrearTierUnitDetail(2, "BAR", BigDecimal.TEN, 200);
+
+
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 60L)); // tier 3
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 200L)); // tier 2
+
+ final List<UsageInArrearTierUnitDetail> existingUsage = ImmutableList.of(existingFooUsageTier1, existingBarUsageTier2);
+
+ final String existingUsageJson = objectMapper.writeValueAsString(new UsageCapacityInArrearAggregate(existingUsage, BigDecimal.TEN));
+
+ final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+ final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), BigDecimal.TEN, null, currency, null, existingUsageJson);
+ existingItems.add(ii1);
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, UsageDetailMode.AGGREGATE, existingItems);
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("90.00")), 0, String.format("%s != 90.0", result.get(0).getAmount()));
+
+ UsageCapacityInArrearAggregate itemDetails = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageCapacityInArrearAggregate>() {});
+ assertEquals(itemDetails.getAmount().compareTo(new BigDecimal("100.00")), 0);
+ assertEquals(itemDetails.getTierDetails().size(), 2);
+ List<UsageInArrearTierUnitDetail> itemUnitDetails = itemDetails.getTierDetails();
+
+ // BAR item detail
+ assertEquals(itemUnitDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemUnitDetails.get(0).getTier(), 2);
+ assertEquals(itemUnitDetails.get(0).getQuantity().intValue(), 200);
+ assertEquals(itemUnitDetails.get(0).getTierPrice().compareTo(BigDecimal.TEN), 0);
+ // FOO item detail
+ assertEquals(itemUnitDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemUnitDetails.get(1).getTier(), 3);
+ assertEquals(itemUnitDetails.get(1).getQuantity().intValue(), 60);
+ assertEquals(itemUnitDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+ }
+
+ private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, UsageDetailMode usageDetailMode, List<InvoiceItem> existingItems) throws CatalogApiException, InvoiceApiException {
+
+ final LocalDate startDate = new LocalDate(2014, 03, 20);
+ final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
+ final LocalDate endDate = new LocalDate(2014, 05, 15);
+
+ final DefaultUnit unitFoo = new DefaultUnit().setName("FOO");
+ final DefaultUnit unitBar = new DefaultUnit().setName("BAR");
+
+ final DefaultLimit unitFooLimitTier1 = new DefaultLimit().setUnit(unitFoo).setMax((double) 10);
+ final DefaultLimit unitBarLimitTier1 = new DefaultLimit().setUnit(unitBar).setMax((double) 100);
+ final DefaultTier tier1 = createDefaultTierWithLimits(BigDecimal.ONE, unitFooLimitTier1, unitBarLimitTier1);
+
+ final DefaultLimit unitFooLimitTier2 = new DefaultLimit().setUnit(unitFoo).setMax((double) 50);
+ final DefaultLimit unitBarLimitTier2 = new DefaultLimit().setUnit(unitBar).setMax((double) 500);
+ final DefaultTier tier2 = createDefaultTierWithLimits(BigDecimal.TEN, unitFooLimitTier2, unitBarLimitTier2);
+
+ final DefaultLimit unitFooLimitTier3 = new DefaultLimit().setUnit(unitFoo).setMax((double) 75);
+ final DefaultLimit unitBarLimitTier3 = new DefaultLimit().setUnit(unitBar).setMax((double) 750);
+ final DefaultTier tier3 = createDefaultTierWithLimits(new BigDecimal("100.0"), unitFooLimitTier3, unitBarLimitTier3);
+
+ final DefaultUsage usage = createCapacityInArrearUsage(usageName, BillingPeriod.MONTHLY, tier1, tier2, tier3);
+
+ final LocalDate targetDate = endDate;
+
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
+
+ final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
+ final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
+ final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getAmount().compareTo(BigDecimal.ZERO) > 0;
+ }
+ }));
+
+ for (InvoiceItem item : result) {
+ assertEquals(item.getCurrency(), Currency.BTC);
+ assertEquals(item.getAccountId(), accountId);
+ assertEquals(item.getBundleId(), bundleId);
+ assertEquals(item.getSubscriptionId(), subscriptionId);
+ assertEquals(item.getPlanName(), planName);
+ assertEquals(item.getPhaseName(), phaseName);
+ assertEquals(item.getUsageName(), usage.getName());
+ assertTrue(item.getStartDate().compareTo(startDate) == 0);
+ assertTrue(item.getEndDate().compareTo(firstBCDDate) == 0);
+ }
+
+ return result;
+ }
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index 6ad767e..bc7f22c 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -18,6 +18,7 @@
package org.killbill.billing.invoice.usage;
+import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
@@ -33,19 +34,26 @@ import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.TierBlockPolicy;
import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.catalog.api.UsageType;
+import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearAggregate;
+import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUsage;
import org.killbill.billing.usage.api.svcs.DefaultRawUsage;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
@@ -54,6 +62,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearBase {
@@ -78,6 +87,47 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
}
@Test(groups = "fast")
+ public void testBilledDetailsForUnitType() throws JsonProcessingException {
+
+ final LocalDate startDate = new LocalDate(2014, 03, 20);
+ final LocalDate targetDate = startDate.plusDays(1);
+
+ final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 1000, BigDecimal.ONE);
+ final DefaultTier tier = createDefaultTierWithBlocks(block);
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList()));
+
+ final UsageConsumableInArrearTierUnitAggregate detail1 = new UsageConsumableInArrearTierUnitAggregate(3, "FOO", new BigDecimal("0.50"), 1, 700);
+ final UsageConsumableInArrearTierUnitAggregate detail2 = new UsageConsumableInArrearTierUnitAggregate(2, "FOO", BigDecimal.ONE, 1, 500);
+ final UsageConsumableInArrearTierUnitAggregate detail3 = new UsageConsumableInArrearTierUnitAggregate(1, "FOO", BigDecimal.TEN, 1, 10);
+ final UsageConsumableInArrearTierUnitAggregate detail4 = new UsageConsumableInArrearTierUnitAggregate(2, "FOO", BigDecimal.ONE, 1, 50);
+ final UsageConsumableInArrearTierUnitAggregate detail5 = new UsageConsumableInArrearTierUnitAggregate(1, "FOO", BigDecimal.TEN, 1, 100);
+
+ final List<UsageConsumableInArrearTierUnitAggregate> existingUsage = ImmutableList.of(detail1, detail2, detail3, detail4, detail5);
+
+ final UsageConsumableInArrearAggregate usageConsumableInArrearDetail = new UsageConsumableInArrearAggregate(existingUsage);
+
+ final String existingUsageJson = objectMapper.writeValueAsString(usageConsumableInArrearDetail);
+
+ final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+ final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
+ existingItems.add(ii1);
+
+ final List<UsageConsumableInArrearTierUnitAggregate> aggregateDetails = intervalConsumableInArrear.getBilledDetailsForUnitType(existingItems, "FOO");
+ assertEquals(aggregateDetails.size(), 3);
+ assertEquals(aggregateDetails.get(0).getTier(), 1);
+ assertEquals(aggregateDetails.get(0).getQuantity().intValue(), 110);
+ assertEquals(aggregateDetails.get(1).getTier(), 2);
+ assertEquals(aggregateDetails.get(1).getQuantity().intValue(), 550);
+ assertEquals(aggregateDetails.get(2).getTier(), 3);
+ assertEquals(aggregateDetails.get(2).getQuantity().intValue(), 700);
+
+ }
+
+ @Test(groups = "fast")
public void testComputeToBeBilledUsage() {
final LocalDate startDate = new LocalDate(2014, 03, 20);
@@ -90,8 +140,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = startDate.plusDays(1);
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
);
final List<InvoiceItem> existingUsage = Lists.newArrayList();
@@ -116,7 +166,6 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertEquals(result.compareTo(BigDecimal.TEN.add(BigDecimal.TEN)), 0);
}
-
@Test(groups = "fast")
public void testComputeBilledUsageSizeOneWith_ALL_TIERS() throws CatalogApiException {
@@ -126,27 +175,26 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 1, 100, new BigDecimal("1.0"));
final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
-
final DefaultTieredBlock block3 = createDefaultTieredBlock("unit", 1, 1000, new BigDecimal("0.5"));
final DefaultTier tier3 = createDefaultTierWithBlocks(block3);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier1, tier2, tier3);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
-
- final BigDecimal result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
+ List<UsageConsumableInArrearTierUnitAggregate> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(result.size(), 3);
// 111 = 10 (tier1) + 100 (tier2) + 1 (tier3) => 10 * 1.5 + 100 * 1 + 1 * 0.5 = 115.5
- assertEquals(result, new BigDecimal("115.5"));
+ assertEquals(result.get(0).getAmount(), new BigDecimal("15.0"));
+ assertEquals(result.get(1).getAmount(), new BigDecimal("100.0"));
+ assertEquals(result.get(2).getAmount(), new BigDecimal("0.5"));
}
-
-
@Test(groups = "fast")
public void testComputeBilledUsageWith_ALL_TIERS() throws CatalogApiException {
@@ -159,16 +207,45 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
- final BigDecimal result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L));
+ List<UsageConsumableInArrearTierUnitAggregate> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(result.size(), 2);
// 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15
- assertEquals(result, new BigDecimal("15"));
+ assertEquals(result.get(0).getAmount(), new BigDecimal("10"));
+ assertEquals(result.get(1).getAmount(), new BigDecimal("5"));
+ }
+
+
+ @Test(groups = "fast")
+ public void testComputeBilledUsageWithUnlimitedMaxWith_ALL_TIERS() throws CatalogApiException {
+
+ final DefaultTieredBlock block1 = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
+ final DefaultTier tier1 = createDefaultTierWithBlocks(block1);
+
+ final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 1000, -1, BigDecimal.ONE);
+ final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier1, tier2);
+
+ final LocalDate targetDate = new LocalDate(2014, 03, 20);
+
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
+
+ List<UsageConsumableInArrearTierUnitAggregate> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 5325L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(result.size(), 2);
+
+ // 5000 = 1000 (tier1) + 4325 (tier2) => 10 + 5 = 15
+ assertEquals(result.get(0).getAmount(), new BigDecimal("10"));
+ assertEquals(result.get(1).getAmount(), new BigDecimal("5"));
}
@@ -189,34 +266,68 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
//
// In this model unit amount is first used to figure out which tier we are in, and then we price all unit at that 'target' tier
//
- final BigDecimal inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L));
+ List<UsageConsumableInArrearTierUnitAggregate> inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 1000L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(inputTier1.size(), 1);
// 1000 units => (tier1) : 1000 / 100 + 1000 % 100 = 10
- assertEquals(inputTier1, new BigDecimal("10"));
+ assertEquals(inputTier1.get(0).getAmount(), new BigDecimal("10"));
- final BigDecimal inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L));
+ List<UsageConsumableInArrearTierUnitAggregate> inputTier2 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101000L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(inputTier2.size(), 1);
// 101000 units => (tier2) : 101000 / 1000 + 101000 % 1000 = 101 + 0 = 101
- assertEquals(inputTier2, new BigDecimal("101"));
+ assertEquals(inputTier2.get(0).getAmount(), new BigDecimal("101"));
- final BigDecimal inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L));
+ List<UsageConsumableInArrearTierUnitAggregate> inputTier3 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 101001L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(inputTier3.size(), 1);
// 101001 units => (tier3) : 101001 / 1000 + 101001 % 1000 = 101 + 1 = 102 units => $51
- assertEquals(inputTier3, new BigDecimal("51.0"));
+ assertEquals(inputTier3.get(0).getAmount(), new BigDecimal("51.0"));
// If we pass the maximum of the last tier, we price all units at the last tier
- final BigDecimal inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L));
+ List<UsageConsumableInArrearTierUnitAggregate> inputLastTier = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 300000L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(inputLastTier.size(), 1);
// 300000 units => (tier3) : 300000 / 1000 + 300000 % 1000 = 300 units => $150
- assertEquals(inputLastTier, new BigDecimal("150.0"));
+ assertEquals(inputLastTier.get(0).getAmount(), new BigDecimal("150.0"));
}
@Test(groups = "fast")
+ public void testComputeBilledUsageWithUnlimitedMaxWith_TOP_TIER() throws CatalogApiException {
+
+ final DefaultTieredBlock block1 = createDefaultTieredBlock("unit", 100, 10, BigDecimal.TEN);
+ final DefaultTier tier1 = createDefaultTierWithBlocks(block1);
+
+ final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 100, -1, BigDecimal.ONE);
+ final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
+
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.TOP_TIER, tier1, tier2);
+
+ final LocalDate targetDate = new LocalDate(2014, 03, 20);
+
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
+ //
+ // In this model unit amount is first used to figure out which tier we are in, and then we price all unit at that 'target' tier
+ //
+ List<UsageConsumableInArrearTierUnitAggregate> inputTier1 = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 2000L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ // Target tier 2:
+ assertEquals(inputTier1.size(), 1);
+ assertEquals(inputTier1.get(0).getAmount(), new BigDecimal("20"));
+ }
+
+
+
+
+ @Test(groups = "fast")
public void testComputeBilledUsageSizeOneWith_TOP_TIER() throws CatalogApiException {
final DefaultTieredBlock block1 = createDefaultTieredBlock("unit", 1, 10, new BigDecimal("1.5"));
@@ -225,30 +336,27 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final DefaultTieredBlock block2 = createDefaultTieredBlock("unit", 1, 100, new BigDecimal("1.0"));
final DefaultTier tier2 = createDefaultTierWithBlocks(block2);
-
final DefaultTieredBlock block3 = createDefaultTieredBlock("unit", 1, 1000, new BigDecimal("0.5"));
final DefaultTier tier3 = createDefaultTierWithBlocks(block3);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.TOP_TIER, tier1, tier2, tier3);
final LocalDate targetDate = new LocalDate(2014, 03, 20);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
- final BigDecimal result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L));
+ List<UsageConsumableInArrearTierUnitAggregate> result = intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("unit", 111L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of());
+ assertEquals(result.size(), 1);
// 111 = 111 * 0.5 =
- assertEquals(result, new BigDecimal("55.5"));
+ assertEquals(result.get(0).getAmount(), new BigDecimal("55.5"));
}
-
-
-
@Test(groups = "fast")
- public void testComputeMissingItems() throws CatalogApiException {
+ public void testComputeMissingItems() throws CatalogApiException, IOException, InvoiceApiException {
final LocalDate startDate = new LocalDate(2014, 03, 20);
final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
@@ -267,7 +375,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = endDate;
- final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, event1, event2);
@@ -290,7 +398,6 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
}
}));
-
// Invoiced for 1 BTC and used 130 + 271 = 401 => 5 blocks => 5 BTC so remaining piece should be 4 BTC
assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("4.0")), 0, String.format("%s != 4.0", result.get(0).getAmount()));
assertEquals(result.get(0).getCurrency(), Currency.BTC);
@@ -303,6 +410,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertTrue(result.get(0).getStartDate().compareTo(startDate) == 0);
assertTrue(result.get(0).getEndDate().compareTo(firstBCDDate) == 0);
+ assertNotNull(result.get(0).getItemDetails());
+ UsageConsumableInArrearAggregate usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ List<UsageConsumableInArrearTierUnitAggregate> itemDetails = usageDetail.getTierDetails();
+ assertEquals(itemDetails.size(), 1);
+ // Because we did not have the details before, the new details don't take into account the
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("5.0")), 0);
+
// Invoiced for 1 BTC and used 199 => 2 blocks => 2 BTC so remaining piece should be 1 BTC
assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("1.0")), 0, String.format("%s != 1.0", result.get(0).getAmount()));
assertEquals(result.get(1).getCurrency(), Currency.BTC);
@@ -314,6 +428,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertEquals(result.get(1).getUsageName(), usage.getName());
assertTrue(result.get(1).getStartDate().compareTo(firstBCDDate) == 0);
assertTrue(result.get(1).getEndDate().compareTo(endDate) == 0);
+ assertNotNull(result.get(1).getItemDetails());
+
+ usageDetail = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ itemDetails = usageDetail.getTierDetails();
+ assertEquals(itemDetails.size(), 1);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("2.0")), 0);
+
}
@Test(groups = "fast")
@@ -323,10 +444,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final DefaultTieredBlock tieredBlock2 = createDefaultTieredBlock("unit2", 10, 1000, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(tieredBlock1, tieredBlock2);
-
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
-
final LocalDate t0 = new LocalDate(2015, 03, BCD);
final BillingEvent eventT0 = createMockBillingEvent(t0.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
@@ -341,7 +460,6 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = t3;
-
// Prev t0
final RawUsage raw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 01), "unit", 12L);
@@ -363,30 +481,27 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsage, targetDate, true, eventT0, eventT1, eventT2, eventT3);
-
- final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage();
+ final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage();
Assert.assertEquals(unsortedRolledUpUsage.size(), 2);
final List<RolledUpUsage> rolledUpUsage = TEST_ROLLED_UP_FIRST_USAGE_ORDERING.sortedCopy(unsortedRolledUpUsage);
Assert.assertEquals(rolledUpUsage.get(0).getStart().compareTo(t0), 0);
Assert.assertEquals(rolledUpUsage.get(0).getEnd().compareTo(t1), 0);
- Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().size(),1);
+ Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().size(), 1);
Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().get(0).getUnitType(), "unit");
Assert.assertEquals(rolledUpUsage.get(0).getRolledUpUnits().get(0).getAmount(), new Long(10L));
Assert.assertEquals(rolledUpUsage.get(1).getStart().compareTo(t2), 0);
Assert.assertEquals(rolledUpUsage.get(1).getEnd().compareTo(t3), 0);
- Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().size(),2);
+ Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().size(), 2);
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(0).getUnitType(), "unit");
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(0).getAmount(), new Long(20L));
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(1).getUnitType(), "unit2");
Assert.assertEquals(rolledUpUsage.get(1).getRolledUpUnits().get(1).getAmount(), new Long(21L));
}
-
-
- @Test(groups = "fast", description="See https://github.com/killbill/killbill/issues/706")
+ @Test(groups = "fast", description = "See https://github.com/killbill/killbill/issues/706")
public void testWithRawUsageStartDateAfterEndDate() throws CatalogApiException {
final LocalDate startDate = new LocalDate(2014, 10, 16);
@@ -402,12 +517,13 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final DefaultTier tier = createDefaultTierWithBlocks(block);
final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
-
- final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
final BillingEvent event2 = createMockBillingEvent(new LocalDate(2014, 10, 16).toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final ContiguousIntervalUsageInArrear intervalConsumableInArrear = usage.getUsageType() == UsageType.CAPACITY ?
+ new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext) :
+ new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext);
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, rawUsageStartDate, internalCallContext);
intervalConsumableInArrear.addBillingEvent(event1);
intervalConsumableInArrear.addBillingEvent(event2);
@@ -415,4 +531,522 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
assertEquals(res.getTransitionTimes().size(), 0);
}
+ @Test(groups = "fast")
+ public void testBilledUsage() throws CatalogApiException {
+
+ final DefaultTieredBlock block1 = createDefaultTieredBlock("cell-phone-minutes", 1000, 10000, new BigDecimal("0.5"));
+ final DefaultTieredBlock block2 = createDefaultTieredBlock("Mbytes", 512, 512000, new BigDecimal("0.3"));
+ final DefaultTier tier = createDefaultTierWithBlocks(block1, block2);
+
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, TierBlockPolicy.ALL_TIERS, tier);
+ final LocalDate targetDate = new LocalDate(2014, 03, 20);
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
+ final List<UsageConsumableInArrearTierUnitAggregate> tierUnitDetails = Lists.newArrayList();
+ tierUnitDetails.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("cell-phone-minutes", 1000L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of()));
+ tierUnitDetails.addAll(intervalConsumableInArrear.computeToBeBilledConsumableInArrear(new DefaultRolledUpUnit("Mbytes", 30720L), ImmutableList.<UsageConsumableInArrearTierUnitAggregate>of()));
+ assertEquals(tierUnitDetails.size(), 2);
+
+ final UsageConsumableInArrearAggregate details = new UsageConsumableInArrearAggregate(tierUnitDetails);
+
+ assertEquals(details.getAmount().compareTo(new BigDecimal("18.5")), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeMissingItemsAggregateModeAllTier_AGGREGATE() throws CatalogApiException, IOException, InvoiceApiException {
+
+ // Case 1
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")), 0);
+
+ UsageConsumableInArrearAggregate usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ List<UsageConsumableInArrearTierUnitAggregate> itemDetails = usageDetail.getTierDetails();
+
+ // BAR: 99 * 2 = 198
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 1);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 99);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(1).getTier(), 1);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // Case 2
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("225")), 0);
+
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ itemDetails = usageDetail.getTierDetails();
+
+ // BAR: 100 * 2 = 200
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 1);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 100);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.0")), 0);
+ // BAR: 1 * 20 = 20
+ assertEquals(itemDetails.get(1).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(1).getTier(), 2);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("20.0")), 0);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 1);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(itemDetails.get(2).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(2).getTier(), 1);
+ assertEquals(itemDetails.get(2).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(itemDetails.get(2).getQuantity().intValue(), 5);
+ assertEquals(itemDetails.get(2).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // Case 3
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2230")), 0);
+
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ itemDetails = usageDetail.getTierDetails();
+ // BAR: 100 * 2 = 200
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 1);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 100);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.0")), 0);
+ // BAR: 1 * 20 = 20
+ assertEquals(itemDetails.get(1).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(1).getTier(), 2);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("20.0")), 0);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 1);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 10 * 1 = 10
+ assertEquals(itemDetails.get(2).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(2).getTier(), 1);
+ assertEquals(itemDetails.get(2).getAmount().compareTo(BigDecimal.TEN), 0);
+ assertEquals(itemDetails.get(2).getQuantity().intValue(), 10);
+ assertEquals(itemDetails.get(2).getTierPrice().compareTo(BigDecimal.ONE), 0);
+ // FOO: 50 * 10 = 500
+ assertEquals(itemDetails.get(3).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(3).getTier(), 2);
+ assertEquals(itemDetails.get(3).getAmount().compareTo(new BigDecimal("500")), 0);
+ assertEquals(itemDetails.get(3).getQuantity().intValue(), 50);
+ assertEquals(itemDetails.get(3).getTierPrice().compareTo(BigDecimal.TEN), 0);
+ // FOO: 15 * 100 = 1500
+ assertEquals(itemDetails.get(4).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(4).getTier(), 3);
+ assertEquals(itemDetails.get(4).getAmount().compareTo(new BigDecimal("1500")), 0);
+ assertEquals(itemDetails.get(4).getQuantity().intValue(), 15);
+ assertEquals(itemDetails.get(4).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeMissingItemsDetailModeAllTier_DETAIL() throws CatalogApiException, IOException, InvoiceApiException {
+
+ // Case 1
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 2);
+ // BAR: 99 * 2 = 198
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 99);
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("2.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 5);
+ assertEquals(result.get(1).getRate().compareTo(BigDecimal.ONE), 0);
+
+ // Case 2
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 3);
+ // BAR: 100 * 2 = 200
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 100);
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("2.0")), 0);
+ // BAR: 1 * 20 = 20
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("20.0")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 1);
+ assertEquals(result.get(1).getRate().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(result.get(2).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(result.get(2).getQuantity().intValue(), 5);
+ assertEquals(result.get(2).getRate().compareTo(BigDecimal.ONE), 0);
+
+ // Case 3
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 5);
+ // BAR: 100 * 2 = 200
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("200.0")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 100);
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("2.0")), 0);
+ // BAR: 1 * 20 = 20
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("20.0")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 1);
+ assertEquals(result.get(1).getRate().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 10 * 1 = 10
+ assertEquals(result.get(2).getAmount().compareTo(BigDecimal.TEN), 0);
+ assertEquals(result.get(2).getQuantity().intValue(), 10);
+ assertEquals(result.get(2).getRate().compareTo(BigDecimal.ONE), 0);
+ // FOO: 50 * 10 = 500
+ assertEquals(result.get(3).getAmount().compareTo(new BigDecimal("500")), 0);
+ assertEquals(result.get(3).getQuantity().intValue(), 50);
+ assertEquals(result.get(3).getRate().compareTo(BigDecimal.TEN), 0);
+ // FOO: 15 * 100 = 1500
+ assertEquals(result.get(4).getAmount().compareTo(new BigDecimal("1500")), 0);
+ assertEquals(result.get(4).getQuantity().intValue(), 15);
+ assertEquals(result.get(4).getRate().compareTo(new BigDecimal("100.0")), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeMissingItemsAggregateModeTopTier_AGGREGATE() throws CatalogApiException, IOException, InvoiceApiException {
+
+ // Case 1
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("203")), 0);
+
+ UsageConsumableInArrearAggregate usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ List<UsageConsumableInArrearTierUnitAggregate> itemDetails = usageDetail.getTierDetails();
+ // BAR: 99 * 2 = 198
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 1);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 99);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(1).getTier(), 1);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // Case 2
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2025")), 0);
+
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ itemDetails = usageDetail.getTierDetails();
+
+ // BAR: 101 * 20 = 2020
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 2);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 101);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(1).getTier(), 1);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 5);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(BigDecimal.ONE), 0);
+
+ // Case 3
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("9620")), 0);
+
+ usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ itemDetails = usageDetail.getTierDetails();
+ // BAR: 101 * 20 = 2020
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 2);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 101);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 76 * 100 = 7500
+ assertEquals(itemDetails.get(1).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(1).getTier(), 3);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("7600")), 0);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 76);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("100.0")), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testComputeMissingItemsDetailModeTopTier_DETAIL() throws CatalogApiException, IOException, InvoiceApiException {
+
+ // Case 1
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L));
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 2);
+ // BAR: 99 * 2 = 198
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("198")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 99);
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("2.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 5);
+ assertEquals(result.get(1).getRate().compareTo(BigDecimal.ONE), 0);
+
+ // Case 2
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 2);
+ // BAR: 101 * 20 = 2020
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 101);
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 5 * 1 = 5
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("5")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 5);
+ assertEquals(result.get(1).getRate().compareTo(BigDecimal.ONE), 0);
+
+ // Case 3
+ rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L));
+
+ result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
+ assertEquals(result.size(), 2);
+ // BAR: 101 * 20 = 2020
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("2020.0")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 101);
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("20.0")), 0);
+ // FOO: 76 * 100 = 7500
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("7600")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 76);
+ assertEquals(result.get(1).getRate().compareTo(new BigDecimal("100.0")), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersWithExistingItemsAllTiers_AGGREGATE() throws CatalogApiException, IOException, InvoiceApiException {
+
+ //
+ // Let's assume we were already billed on the previous period
+ //
+ // FOO : 10 (tier 1) + 40 (tier 2) = 50
+ final UsageConsumableInArrearTierUnitAggregate existingFooUsageTier1 = new UsageConsumableInArrearTierUnitAggregate(1, "FOO", BigDecimal.ONE, 1, 10, new BigDecimal("10.00"));
+ final UsageConsumableInArrearTierUnitAggregate existingFooUsageTier2 = new UsageConsumableInArrearTierUnitAggregate(2, "FOO", BigDecimal.TEN, 1, 40, new BigDecimal("400.00"));
+ // BAR : 10 (tier 1) + 40 (tier 2)
+ final UsageConsumableInArrearTierUnitAggregate existingBarUsageTier1 = new UsageConsumableInArrearTierUnitAggregate(1, "BAR", new BigDecimal("2.00"), 1, 80, new BigDecimal("160.00"));
+
+ final List<UsageConsumableInArrearTierUnitAggregate> existingUsage = ImmutableList.of(existingFooUsageTier1, existingFooUsageTier2, existingBarUsageTier1);
+
+ final UsageConsumableInArrearAggregate usageConsumableInArrearDetail = new UsageConsumableInArrearAggregate(existingUsage);
+
+ final String existingUsageJson = objectMapper.writeValueAsString(usageConsumableInArrearDetail);
+
+ //
+ // Create usage data points (will include already billed + add new usage data)
+ //
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 50L /* already built */ + 20L)); // tier 3
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L)); // tier 2
+
+ final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+ final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
+ existingItems.add(ii1);
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, existingItems);
+ assertEquals(result.size(), 1);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("3140.00")), 0, String.format("%s != 3140.0", result.get(0).getAmount()));
+
+ UsageConsumableInArrearAggregate usageDetail = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearAggregate>() {});
+ List<UsageConsumableInArrearTierUnitAggregate> itemDetails = usageDetail.getTierDetails();
+
+ // We get same total than AGGREGATE : 3140
+
+ // BAR item detail
+ assertEquals(itemDetails.get(0).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(0).getTier(), 1);
+ assertEquals(itemDetails.get(0).getTierBlockSize(), 1);
+ assertEquals(itemDetails.get(0).getQuantity().intValue(), 20);
+ assertEquals(itemDetails.get(0).getTierPrice().compareTo(new BigDecimal("2.00")), 0);
+ assertEquals(itemDetails.get(0).getAmount().compareTo(new BigDecimal("40.00")), 0);
+
+ assertEquals(itemDetails.get(1).getTierUnit(), "BAR");
+ assertEquals(itemDetails.get(1).getTier(), 2);
+ assertEquals(itemDetails.get(1).getTierBlockSize(), 1);
+ assertEquals(itemDetails.get(1).getQuantity().intValue(), 100);
+ assertEquals(itemDetails.get(1).getTierPrice().compareTo(new BigDecimal("20.00")), 0);
+ assertEquals(itemDetails.get(1).getAmount().compareTo(new BigDecimal("2000.00")), 0);
+
+ // FOO item detail
+ assertEquals(itemDetails.get(2).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(2).getTier(), 2);
+ assertEquals(itemDetails.get(2).getTierBlockSize(), 1);
+ assertEquals(itemDetails.get(2).getQuantity().intValue(), 10);
+ assertEquals(itemDetails.get(2).getTierPrice().compareTo(new BigDecimal("10.00")), 0);
+ assertEquals(itemDetails.get(2).getAmount().compareTo(new BigDecimal("100.00")), 0);
+
+ assertEquals(itemDetails.get(3).getTierUnit(), "FOO");
+ assertEquals(itemDetails.get(3).getTier(), 3);
+ assertEquals(itemDetails.get(3).getTierBlockSize(), 1);
+ assertEquals(itemDetails.get(3).getQuantity().intValue(), 10);
+ assertEquals(itemDetails.get(3).getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+ assertEquals(itemDetails.get(3).getAmount().compareTo(new BigDecimal("1000.00")), 0);
+ }
+
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersWithExistingItemsAllTiers_DETAIL() throws CatalogApiException, IOException, InvoiceApiException {
+
+ //
+ // Create usage data points (will include already billed + add new usage data)
+ //
+ List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 50L /* already built */ + 20L)); // tier 3
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L)); // tier 2
+
+ // FOO : 10 (tier 1) + 40 (tier 2) = 50
+ final UsageConsumableInArrearTierUnitAggregate existingFooUsageTier1 = new UsageConsumableInArrearTierUnitAggregate(1, "FOO", BigDecimal.ONE, 1, 10, new BigDecimal("10.00"));
+ final String usageInArrearDetail1 = objectMapper.writeValueAsString(existingFooUsageTier1);
+
+ final UsageConsumableInArrearTierUnitAggregate existingFooUsageTier2 = new UsageConsumableInArrearTierUnitAggregate(2, "FOO", BigDecimal.TEN, 1, 40, new BigDecimal("400.00"));
+ final String usageInArrearDetail2 = objectMapper.writeValueAsString(existingFooUsageTier2);
+ // BAR : 10 (tier 1) + 40 (tier 2)
+ final UsageConsumableInArrearTierUnitAggregate existingBarUsageTier1 = new UsageConsumableInArrearTierUnitAggregate(1, "BAR", new BigDecimal("2.00"), 1, 80, new BigDecimal("160.00"));
+ final String usageInArrearDetail3 = objectMapper.writeValueAsString(existingBarUsageTier1);
+
+ // Same as previous example bu instead of creating JSON we create one item per type/tier
+ final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+ final InvoiceItem i1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("10.00") /* amount */, new BigDecimal("1.00") /* rate = tierPrice*/, currency, 10 /* # units*/, usageInArrearDetail1);
+ final InvoiceItem i2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("400.00"), new BigDecimal("10.00"), currency, 40, usageInArrearDetail2);
+ final InvoiceItem i3 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("160.00"), new BigDecimal("2.00"), currency, 80, usageInArrearDetail3);
+ existingItems.addAll(ImmutableList.<InvoiceItem>of(i1, i2, i3));
+
+ List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, existingItems);
+ assertEquals(result.size(), 4);
+ final UsageConsumableInArrearTierUnitAggregate resultUsageInArrearDetail0 = objectMapper.readValue(result.get(0).getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {});
+ assertEquals(resultUsageInArrearDetail0.getTierUnit(), "BAR");
+ assertEquals(resultUsageInArrearDetail0.getTier(), 1);
+ assertEquals(resultUsageInArrearDetail0.getQuantity().intValue(), 20);
+ assertEquals(resultUsageInArrearDetail0.getTierPrice().compareTo(new BigDecimal("2.00")), 0);
+
+ assertEquals(result.get(0).getRate().compareTo(new BigDecimal("2.00")), 0);
+ assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("40.00")), 0);
+ assertEquals(result.get(0).getQuantity().intValue(), 20);
+
+ final UsageConsumableInArrearTierUnitAggregate resultUsageInArrearDetail1 = objectMapper.readValue(result.get(1).getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {});
+ assertEquals(resultUsageInArrearDetail1.getTierUnit(), "BAR");
+ assertEquals(resultUsageInArrearDetail1.getTier(), 2);
+ assertEquals(resultUsageInArrearDetail1.getQuantity().intValue(), 100);
+ assertEquals(resultUsageInArrearDetail1.getTierPrice().compareTo(new BigDecimal("20.00")), 0);
+
+ assertEquals(result.get(1).getRate().compareTo(new BigDecimal("20.00")), 0);
+ assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("2000.00")), 0);
+ assertEquals(result.get(1).getQuantity().intValue(), 100);
+
+ final UsageConsumableInArrearTierUnitAggregate resultUsageInArrearDetail2 = objectMapper.readValue(result.get(2).getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {});
+ assertEquals(resultUsageInArrearDetail2.getTierUnit(), "FOO");
+ assertEquals(resultUsageInArrearDetail2.getTier(), 2);
+ assertEquals(resultUsageInArrearDetail2.getQuantity().intValue(), 10);
+ assertEquals(resultUsageInArrearDetail2.getTierPrice().compareTo(new BigDecimal("10.00")), 0);
+
+ assertEquals(result.get(2).getRate().compareTo(new BigDecimal("10.00")), 0);
+ assertEquals(result.get(2).getAmount().compareTo(new BigDecimal("100.00")), 0);
+ assertEquals(result.get(2).getQuantity().intValue(), 10);
+
+
+ final UsageConsumableInArrearTierUnitAggregate resultUsageInArrearDetail3 = objectMapper.readValue(result.get(3).getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {});
+ assertEquals(resultUsageInArrearDetail3.getTierUnit(), "FOO");
+ assertEquals(resultUsageInArrearDetail3.getTier(), 3);
+ assertEquals(resultUsageInArrearDetail3.getQuantity().intValue(), 10);
+ assertEquals(resultUsageInArrearDetail3.getTierPrice().compareTo(new BigDecimal("100.00")), 0);
+
+ assertEquals(result.get(3).getRate().compareTo(new BigDecimal("100.00")), 0);
+ assertEquals(result.get(3).getAmount().compareTo(new BigDecimal("1000.00")), 0);
+ assertEquals(result.get(3).getQuantity().intValue(), 10);
+
+ }
+
+ @Test(groups = "fast")
+ public void testMultipleItemsAndTiersWithExistingItemsTopTier() throws CatalogApiException, IOException {
+ // TODO + code
+ }
+
+ private List<InvoiceItem> produceInvoiceItems(List<RawUsage> rawUsages, TierBlockPolicy tierBlockPolicy, UsageDetailMode usageDetailMode, final List<InvoiceItem> existingItems) throws CatalogApiException, InvoiceApiException {
+
+ final LocalDate startDate = new LocalDate(2014, 03, 20);
+ final LocalDate firstBCDDate = new LocalDate(2014, 04, 15);
+ final LocalDate endDate = new LocalDate(2014, 05, 15);
+
+ final DefaultTieredBlock blockFooTier1 = createDefaultTieredBlock("FOO", 1, 10, BigDecimal.ONE);
+ final DefaultTieredBlock blockBarTier1 = createDefaultTieredBlock("BAR", 1, 100, new BigDecimal("2"));
+ final DefaultTier tier1 = createDefaultTierWithBlocks(blockFooTier1, blockBarTier1);
+
+ final DefaultTieredBlock blockFooTier2 = createDefaultTieredBlock("FOO", 1, 50, BigDecimal.TEN);
+ final DefaultTieredBlock blockBarTier2 = createDefaultTieredBlock("BAR", 1, 500, new BigDecimal("20"));
+ final DefaultTier tier2 = createDefaultTierWithBlocks(blockFooTier2, blockBarTier2);
+
+ final DefaultTieredBlock blockFooTier3 = createDefaultTieredBlock("FOO", 1, 75, new BigDecimal("100"));
+ final DefaultTieredBlock blockBarTier3 = createDefaultTieredBlock("BAR", 1, 750, new BigDecimal("200"));
+ final DefaultTier tier3 = createDefaultTierWithBlocks(blockFooTier3, blockBarTier3);
+ final DefaultUsage usage = createConsumableInArrearUsage(usageName, BillingPeriod.MONTHLY, tierBlockPolicy, tier1, tier2, tier3);
+
+ final LocalDate targetDate = endDate;
+
+ final BillingEvent event1 = createMockBillingEvent(startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+ final BillingEvent event2 = createMockBillingEvent(endDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), BillingPeriod.MONTHLY, Collections.<Usage>emptyList());
+
+ final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
+
+ final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
+ final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
+ final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
+ @Override
+ public boolean apply(final InvoiceItem input) {
+ return input.getAmount().compareTo(BigDecimal.ZERO) > 0;
+ }
+ }));
+
+ for (InvoiceItem item : result) {
+ assertEquals(item.getCurrency(), Currency.BTC);
+ assertEquals(item.getAccountId(), accountId);
+ assertEquals(item.getBundleId(), bundleId);
+ assertEquals(item.getSubscriptionId(), subscriptionId);
+ assertEquals(item.getPlanName(), planName);
+ assertEquals(item.getPhaseName(), phaseName);
+ assertEquals(item.getUsageName(), usage.getName());
+ assertTrue(item.getStartDate().compareTo(startDate) == 0);
+ assertTrue(item.getEndDate().compareTo(firstBCDDate) == 0);
+ }
+
+ return result;
+ }
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
index eb02634..17831a6 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
@@ -75,7 +75,7 @@ public class TestSubscriptionConsumableInArrear extends TestUsageInArrearBase {
LocalDate targetDate = new LocalDate(2013, 6, 23);
- final SubscriptionUsageInArrear foo = new SubscriptionUsageInArrear(accountId, invoiceId, billingEvents, ImmutableList.<RawUsage>of(), targetDate, new LocalDate(dt1, DateTimeZone.UTC), internalCallContext);
+ final SubscriptionUsageInArrear foo = new SubscriptionUsageInArrear(accountId, invoiceId, billingEvents, ImmutableList.<RawUsage>of(), targetDate, new LocalDate(dt1, DateTimeZone.UTC), usageDetailMode, internalCallContext);
final List<ContiguousIntervalUsageInArrear> result = foo.computeInArrearUsageInterval();
assertEquals(result.size(), 3);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
index 0c1f557..42c1709 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestUsageInArrearBase.java
@@ -22,7 +22,6 @@ import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.catalog.DefaultInternationalPrice;
@@ -44,6 +43,8 @@ import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.usage.RawUsage;
+import org.killbill.billing.util.config.definition.InvoiceConfig.UsageDetailMode;
+import org.killbill.billing.util.jackson.ObjectMapper;
import org.mockito.Mockito;
import org.testng.annotations.BeforeClass;
@@ -58,6 +59,8 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
protected String phaseName;
protected Currency currency;
protected String usageName;
+ protected ObjectMapper objectMapper;
+
@BeforeClass(groups = "fast")
protected void beforeClass() throws Exception {
@@ -71,10 +74,31 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
planName = "planName";
phaseName = "phaseName";
currency = Currency.BTC;
+ usageDetailMode = invoiceConfig.getItemResultBehaviorMode(internalCallContext);
+ objectMapper = new ObjectMapper();
+ }
+
+
+ protected ContiguousIntervalCapacityUsageInArrear createContiguousIntervalCapacityInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+ return createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, closedInterval, usageDetailMode, events);
+ }
+
+ protected ContiguousIntervalCapacityUsageInArrear createContiguousIntervalCapacityInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
+ for (final BillingEvent event : events) {
+ intervalCapacityInArrear.addBillingEvent(event);
+ }
+ intervalCapacityInArrear.build(closedInterval);
+ return intervalCapacityInArrear;
+ }
+
+
+ protected ContiguousIntervalConsumableUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
+ return createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, closedInterval, usageDetailMode, events);
}
- protected ContiguousIntervalUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, final BillingEvent... events) {
- final ContiguousIntervalUsageInArrear intervalConsumableInArrear = new ContiguousIntervalUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), internalCallContext);
+ protected ContiguousIntervalConsumableUsageInArrear createContiguousIntervalConsumableInArrear(final DefaultUsage usage, final List<RawUsage> rawUsages, final LocalDate targetDate, final boolean closedInterval, UsageDetailMode detailMode, final BillingEvent... events) {
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
for (final BillingEvent event : events) {
intervalConsumableInArrear.addBillingEvent(event);
}
jaxrs/pom.xml 2(+1 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 7b9ead4..f1ea5ee 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
index 8b880a6..5a93bc8 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/CatalogJson.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -106,38 +106,11 @@ public class CatalogJson {
}
ProductJson productJson = productMap.get(product.getName());
if (productJson == null) {
- productJson = new ProductJson(product.getCategory().toString(),
- product.getName(),
- product.getPrettyName(),
- toProductNames(product.getIncluded()),
- toProductNames(product.getAvailable()));
+ productJson = new ProductJson(product);
productMap.put(product.getName(), productJson);
}
- // Build the phases associated with this plan
- final List<PhaseJson> phases = new LinkedList<PhaseJson>();
- for (final PlanPhase phase : plan.getAllPhases()) {
- final List<PriceJson> prices = new LinkedList<PriceJson>();
- if (phase.getRecurring() != null && phase.getRecurring().getRecurringPrice() != null) {
- for (final Price price : phase.getRecurring().getRecurringPrice().getPrices()) {
- prices.add(new PriceJson(price));
- }
- }
-
- final List<PriceJson> fixedPrices = new LinkedList<PriceJson>();
- if (phase.getFixed() != null && phase.getFixed().getPrice() != null) {
- for (final Price price : phase.getFixed().getPrice().getPrices()) {
- fixedPrices.add(new PriceJson(price));
- }
- }
-
- final DurationJson durationJson = new DurationJson(phase.getDuration().getUnit(), phase.getDuration().getNumber());
- final List<UsageJson> usagesJson = buildUsagesJson(phase.getUsages());
- final PhaseJson phaseJson = new PhaseJson(phase.getPhaseType().toString(), prices, fixedPrices, durationJson, usagesJson);
- phases.add(phaseJson);
- }
-
- final PlanJson planJson = new PlanJson(plan.getName(), plan.getPrettyName(), plan.getRecurringBillingPeriod(), phases);
+ final PlanJson planJson = new PlanJson(plan);
productJson.getPlans().add(planJson);
}
@@ -150,53 +123,7 @@ public class CatalogJson {
}
- private List<UsageJson> buildUsagesJson(final Usage[] usages) throws CurrencyValueNull {
- List<UsageJson> usagesJson = new ArrayList<UsageJson>();
- for (int i = 0; i < usages.length; i++) {
- usagesJson.add(new UsageJson(usages[i].getBillingPeriod().toString(), buildTiers(usages[i].getTiers())));
- }
- return usagesJson;
- }
-
- private List<TierJson> buildTiers(final Tier[] tiers) throws CurrencyValueNull {
- List<TierJson> tiersJson = new ArrayList<TierJson>();
- if (tiers != null && tiers.length > 0) {
- for (int i=0; i < tiers.length; i++) {
- tiersJson.add(new TierJson(buildTieredBlocks(tiers[i].getTieredBlocks()),
- buildLimits(tiers[i].getLimits()),
- buildPrices(tiers[i].getFixedPrice()),
- buildPrices(tiers[i].getRecurringPrice())));
- }
- }
- return tiersJson;
- }
-
- private List<LimitJson> buildLimits(final Limit[] limits) {
- List<LimitJson> limitsJson = new ArrayList<LimitJson>();
- if (limits != null && limits.length > 0) {
- for (int i=0; i < limits.length; i++) {
- limitsJson.add(new LimitJson(limits[i].getUnit().getName(),
- limits[i].getMax().toString(),
- limits[i].getMin().toString()));
- }
- }
- return limitsJson;
- }
-
- private List<TieredBlockJson> buildTieredBlocks(final TieredBlock[] tieredBlocks) throws CurrencyValueNull {
- List<TieredBlockJson> tieredBlocksJson = new ArrayList<TieredBlockJson>();
- if (tieredBlocks != null && tieredBlocks.length > 0) {
- for (int i=0; i < tieredBlocks.length; i++) {
- tieredBlocksJson.add(new TieredBlockJson(tieredBlocks[i].getUnit().getName(),
- tieredBlocks[i].getSize().toString(),
- tieredBlocks[i].getMax().toString(),
- buildPrices(tieredBlocks[i].getPrice())));
- }
- }
- return tieredBlocksJson;
- }
-
- private List<PriceJson> buildPrices(final InternationalPrice internationalPrice) throws CurrencyValueNull {
+ private static List<PriceJson> buildPrices(final InternationalPrice internationalPrice) throws CurrencyValueNull {
List<PriceJson> pricesJson = new ArrayList<PriceJson>();
Price[] prices = (internationalPrice != null) ? internationalPrice.getPrices() : null;
if (prices != null && prices.length > 0) {
@@ -208,16 +135,6 @@ public class CatalogJson {
return pricesJson;
}
- private List<String> toProductNames(final Collection<Product> in) {
- return Lists.transform(ImmutableList.<Product>copyOf(in),
- new Function<Product, String>() {
- @Override
- public String apply(final Product input) {
- return input.getName();
- }
- });
- }
-
public List<ProductJson> getProducts() {
return products;
}
@@ -373,8 +290,13 @@ public class CatalogJson {
this.available = available;
}
- public ProductJson(final String type, final String name, final String prettyName, final List<String> included, final List<String> available) {
- this(type, name, prettyName, new LinkedList<PlanJson>(), included, available);
+ public ProductJson(final Product product) {
+ this.type = product.getCategory().toString();
+ this.name = product.getName();
+ this.prettyName = product.getPrettyName();
+ this.plans = new LinkedList<PlanJson>();
+ this.included = toProductNames(product.getIncluded());
+ this.available = toProductNames(product.getAvailable());
}
public String getType() {
@@ -453,6 +375,16 @@ public class CatalogJson {
result = 31 * result + (available != null ? available.hashCode() : 0);
return result;
}
+
+ private List<String> toProductNames(final Collection<Product> in) {
+ return Lists.transform(ImmutableList.<Product>copyOf(in),
+ new Function<Product, String>() {
+ @Override
+ public String apply(final Product input) {
+ return input.getName();
+ }
+ });
+ }
}
@ApiModel(value="Plan")
@@ -463,6 +395,19 @@ public class CatalogJson {
private final BillingPeriod billingPeriod;
private final List<PhaseJson> phases;
+ public PlanJson(final Plan plan) throws CurrencyValueNull {
+ final List<PhaseJson> phases = new LinkedList<PhaseJson>();
+ for (final PlanPhase phase : plan.getAllPhases()) {
+ final PhaseJson phaseJson = new PhaseJson(phase);
+ phases.add(phaseJson);
+ }
+
+ this.name = plan.getName();
+ this.prettyName = plan.getPrettyName();
+ this.billingPeriod = plan.getRecurringBillingPeriod();
+ this.phases = phases;
+ }
+
@JsonCreator
public PlanJson(@JsonProperty("name") final String name,
@JsonProperty("prettyName") final String prettyName,
@@ -826,6 +771,31 @@ public class CatalogJson {
private final DurationJson duration;
private final List<UsageJson> usages;
+ public PhaseJson(final PlanPhase phase) throws CurrencyValueNull {
+ final List<PriceJson> prices = new LinkedList<PriceJson>();
+ if (phase.getRecurring() != null && phase.getRecurring().getRecurringPrice() != null) {
+ for (final Price price : phase.getRecurring().getRecurringPrice().getPrices()) {
+ prices.add(new PriceJson(price));
+ }
+ }
+
+ final List<PriceJson> fixedPrices = new LinkedList<PriceJson>();
+ if (phase.getFixed() != null && phase.getFixed().getPrice() != null) {
+ for (final Price price : phase.getFixed().getPrice().getPrices()) {
+ fixedPrices.add(new PriceJson(price));
+ }
+ }
+
+ final DurationJson durationJson = new DurationJson(phase.getDuration().getUnit(), phase.getDuration().getNumber());
+ final List<UsageJson> usagesJson = buildUsagesJson(phase.getUsages());
+
+ this.type = phase.getPhaseType().toString();
+ this.prices = prices;
+ this.fixedPrices = fixedPrices;
+ this.duration = durationJson;
+ this.usages = usagesJson;
+ }
+
@JsonCreator
public PhaseJson(@JsonProperty("type") final String type,
@JsonProperty("prices") final List<PriceJson> prices,
@@ -842,15 +812,19 @@ public class CatalogJson {
public String getType() {
return type;
}
+
public List<PriceJson> getPrices() {
return prices;
}
+
public List<PriceJson> getFixedPrices() {
return fixedPrices;
}
+
public DurationJson getDuration() {
return duration;
}
+
public List<UsageJson> getUsages() {
return usages;
}
@@ -890,7 +864,7 @@ public class CatalogJson {
if (duration != null ? !duration.equals(phaseJson.duration) : phaseJson.duration != null) {
return false;
}
- if (usages != null ? !usages.equals(phaseJson.usages) : phaseJson.usages!= null) {
+ if (usages != null ? !usages.equals(phaseJson.usages) : phaseJson.usages != null) {
return false;
}
@@ -906,6 +880,52 @@ public class CatalogJson {
result = 31 * result + (usages != null ? usages.hashCode() : 0);
return result;
}
+
+ private List<UsageJson> buildUsagesJson(final Usage[] usages) throws CurrencyValueNull {
+ List<UsageJson> usagesJson = new ArrayList<UsageJson>();
+ for (int i = 0; i < usages.length; i++) {
+ usagesJson.add(new UsageJson(usages[i].getBillingPeriod().toString(), buildTiers(usages[i].getTiers())));
+ }
+ return usagesJson;
+ }
+
+ private List<TierJson> buildTiers(final Tier[] tiers) throws CurrencyValueNull {
+ List<TierJson> tiersJson = new ArrayList<TierJson>();
+ if (tiers != null && tiers.length > 0) {
+ for (int i = 0; i < tiers.length; i++) {
+ tiersJson.add(new TierJson(buildTieredBlocks(tiers[i].getTieredBlocks()),
+ buildLimits(tiers[i].getLimits()),
+ buildPrices(tiers[i].getFixedPrice()),
+ buildPrices(tiers[i].getRecurringPrice())));
+ }
+ }
+ return tiersJson;
+ }
+
+ private List<LimitJson> buildLimits(final Limit[] limits) {
+ List<LimitJson> limitsJson = new ArrayList<LimitJson>();
+ if (limits != null && limits.length > 0) {
+ for (int i = 0; i < limits.length; i++) {
+ limitsJson.add(new LimitJson(limits[i].getUnit().getName(),
+ limits[i].getMax().toString(),
+ limits[i].getMin().toString()));
+ }
+ }
+ return limitsJson;
+ }
+
+ private List<TieredBlockJson> buildTieredBlocks(final TieredBlock[] tieredBlocks) throws CurrencyValueNull {
+ List<TieredBlockJson> tieredBlocksJson = new ArrayList<TieredBlockJson>();
+ if (tieredBlocks != null && tieredBlocks.length > 0) {
+ for (int i = 0; i < tieredBlocks.length; i++) {
+ tieredBlocksJson.add(new TieredBlockJson(tieredBlocks[i].getUnit().getName(),
+ tieredBlocks[i].getSize().toString(),
+ tieredBlocks[i].getMax().toString(),
+ buildPrices(tieredBlocks[i].getPrice())));
+ }
+ }
+ return tieredBlocksJson;
+ }
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
index 985d7f2..48c76d3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
@@ -65,8 +65,10 @@ public class InvoiceItemJson extends JsonBase {
private final LocalDate startDate;
private final LocalDate endDate;
private final BigDecimal amount;
+ private final BigDecimal rate;
private final String currency;
- @ApiModelProperty(hidden = true)
+ private final Integer quantity;
+ private final String itemDetails;
private List<InvoiceItemJson> childItems;
@JsonCreator
@@ -88,7 +90,10 @@ public class InvoiceItemJson extends JsonBase {
@JsonProperty("startDate") final LocalDate startDate,
@JsonProperty("endDate") final LocalDate endDate,
@JsonProperty("amount") final BigDecimal amount,
+ @JsonProperty("rate") final BigDecimal rate,
@JsonProperty("currency") final String currency,
+ @JsonProperty("quantity") final Integer quantity,
+ @JsonProperty("itemDetails") final String itemDetails,
@JsonProperty("childItems") final List<InvoiceItemJson> childItems,
@JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
super(auditLogs);
@@ -110,8 +115,11 @@ public class InvoiceItemJson extends JsonBase {
this.startDate = startDate;
this.endDate = endDate;
this.amount = amount;
+ this.rate = rate;
this.currency = currency;
this.childItems = childItems;
+ this.quantity = quantity;
+ this.itemDetails = itemDetails;
}
public InvoiceItemJson(final InvoiceItem item, final List<InvoiceItem> childItems, @Nullable final List<AuditLog> auditLogs) {
@@ -121,7 +129,8 @@ public class InvoiceItemJson extends JsonBase {
item.getPrettyPlanName(), item.getPrettyPhaseName(), item.getPrettyUsageName(),
item.getInvoiceItemType().toString(),
item.getDescription(), item.getStartDate(), item.getEndDate(),
- item.getAmount(), item.getCurrency().name(), toInvoiceItemJson(childItems), toAuditLogJson(auditLogs));
+ item.getAmount(), item.getRate(), item.getCurrency().name(),
+ item.getQuantity(), item.getItemDetails(), toInvoiceItemJson(childItems), toAuditLogJson(auditLogs));
}
private static List<InvoiceItemJson> toInvoiceItemJson(final List<InvoiceItem> childItems) {
@@ -225,7 +234,7 @@ public class InvoiceItemJson extends JsonBase {
@Override
public BigDecimal getRate() {
- return null;
+ return rate;
}
@Override
@@ -234,6 +243,12 @@ public class InvoiceItemJson extends JsonBase {
}
@Override
+ public Integer getQuantity() { return quantity; }
+
+ @Override
+ public String getItemDetails() { return itemDetails; }
+
+ @Override
public boolean matches(final Object o) {
return false;
}
@@ -331,6 +346,8 @@ public class InvoiceItemJson extends JsonBase {
return amount;
}
+ public BigDecimal getRate() { return rate; }
+
public String getCurrency() {
return currency;
}
@@ -339,6 +356,10 @@ public class InvoiceItemJson extends JsonBase {
return childItems;
}
+ public Integer getQuantity() { return quantity; }
+
+ public String getItemDetails() { return itemDetails; }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -357,7 +378,10 @@ public class InvoiceItemJson extends JsonBase {
sb.append(", startDate=").append(startDate);
sb.append(", endDate=").append(endDate);
sb.append(", amount=").append(amount);
+ sb.append(", rate=").append(rate);
sb.append(", currency=").append(currency);
+ sb.append(", quantity=").append(quantity);
+ sb.append(", itemDetails=").append(itemDetails);
sb.append(", childItems=").append(childItems);
sb.append('}');
return sb.toString();
@@ -424,6 +448,15 @@ public class InvoiceItemJson extends JsonBase {
if (childItems != null ? !childItems.equals(that.childItems) : that.childItems != null) {
return false;
}
+ if (quantity != null ? !quantity.equals(that.quantity) : that.quantity != null) {
+ return false;
+ }
+ if (itemDetails != null ? !itemDetails.equals(that.itemDetails) : that.itemDetails != null) {
+ return false;
+ }
+ if (rate != null ? rate.compareTo(that.rate) != 0 : that.rate != null) {
+ return false;
+ }
return true;
}
@@ -444,7 +477,10 @@ public class InvoiceItemJson extends JsonBase {
result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
result = 31 * result + (amount != null ? amount.hashCode() : 0);
+ result = 31 * result + (rate != null ? rate.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ result = 31 * result + (quantity != null ? quantity.hashCode() : 0);
+ result = 31 * result + (itemDetails != null ? itemDetails.hashCode() : 0);
result = 31 * result + (childItems != null ? childItems.hashCode() : 0);
return result;
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index c65a637..29475d4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -488,7 +488,7 @@ public class AccountResource extends JaxRsResourceBase {
final Callable<List<Invoice>> invoicesCallable = new Callable<List<Invoice>>() {
@Override
public List<Invoice> call() throws Exception {
- return invoiceApi.getInvoicesByAccount(accountId, false, tenantContext);
+ return invoiceApi.getInvoicesByAccount(accountId, false, false, tenantContext);
}
};
final Callable<List<InvoicePayment>> invoicePaymentsCallable = new Callable<List<InvoicePayment>>() {
@@ -699,6 +699,7 @@ public class AccountResource extends JaxRsResourceBase {
@QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final boolean withItems,
@QueryParam(QUERY_WITH_MIGRATION_INVOICES) @DefaultValue("false") final boolean withMigrationInvoices,
@QueryParam(QUERY_UNPAID_INVOICES_ONLY) @DefaultValue("false") final boolean unpaidInvoicesOnly,
+ @QueryParam(QUERY_INCLUDE_VOIDED_INVOICES) @DefaultValue("false") final boolean includeVoidedInvoices,
@QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
@javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
@@ -710,7 +711,7 @@ public class AccountResource extends JaxRsResourceBase {
final List<Invoice> invoices = unpaidInvoicesOnly ?
new ArrayList<Invoice>(invoiceApi.getUnpaidInvoicesByAccountId(accountId, null, tenantContext)) :
- invoiceApi.getInvoicesByAccount(accountId, withMigrationInvoices, tenantContext);
+ invoiceApi.getInvoicesByAccount(accountId, withMigrationInvoices, includeVoidedInvoices, tenantContext);
final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
index f85946b..f439655 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AdminResource.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -17,6 +17,7 @@
package org.killbill.billing.jaxrs.resources;
+import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
@@ -300,9 +301,7 @@ public class AdminResource extends JaxRsResourceBase {
generator.close();
} finally {
// In case the client goes away (IOException), make sure to close the underlying DB connection
- while (iterator.hasNext()) {
- iterator.next();
- }
+ tags.close();
}
}
};
@@ -429,7 +428,7 @@ public class AdminResource extends JaxRsResourceBase {
@POST
@Path("/" + HEALTHCHECK)
@Produces(APPLICATION_JSON)
- @ApiOperation(value = "Put the host out of rotation")
+ @ApiOperation(value = "Put the host back into rotation")
@ApiResponses(value = {})
public Response putInRotation(@javax.ws.rs.core.Context final HttpServletRequest request) {
killbillHealthcheck.putInRotation();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
index 4eea0e9..c68ce54 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -18,6 +20,7 @@ package org.killbill.billing.jaxrs.resources;
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
@@ -35,16 +38,30 @@ import javax.ws.rs.core.UriInfo;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
import org.killbill.billing.account.api.AccountUserApi;
import org.killbill.billing.catalog.StandaloneCatalog;
import org.killbill.billing.catalog.VersionedCatalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogUserApi;
+import org.killbill.billing.catalog.api.CurrencyValueNull;
import org.killbill.billing.catalog.api.Listing;
+import org.killbill.billing.catalog.api.Plan;
+import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PriceList;
+import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.SimplePlanDescriptor;
import org.killbill.billing.catalog.api.StaticCatalog;
import org.killbill.billing.catalog.api.user.DefaultSimplePlanDescriptor;
+import org.killbill.billing.entitlement.api.Subscription;
+import org.killbill.billing.entitlement.api.SubscriptionApi;
+import org.killbill.billing.entitlement.api.SubscriptionApiException;
+import org.killbill.billing.entitlement.api.SubscriptionEvent;
import org.killbill.billing.jaxrs.json.CatalogJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.PhaseJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.PlanJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.PriceListJson;
+import org.killbill.billing.jaxrs.json.CatalogJson.ProductJson;
import org.killbill.billing.jaxrs.json.PlanDetailJson;
import org.killbill.billing.jaxrs.json.SimplePlanJson;
import org.killbill.billing.jaxrs.util.Context;
@@ -78,6 +95,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_XML;
public class CatalogResource extends JaxRsResourceBase {
private final CatalogUserApi catalogUserApi;
+ private final SubscriptionApi subscriptionApi;
@Inject
public CatalogResource(final JaxrsUriBuilder uriBuilder,
@@ -87,10 +105,12 @@ public class CatalogResource extends JaxRsResourceBase {
final AccountUserApi accountUserApi,
final PaymentApi paymentApi,
final CatalogUserApi catalogUserApi,
+ final SubscriptionApi subscriptionApi,
final Clock clock,
final Context context) {
super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, null, clock, context);
this.catalogUserApi = catalogUserApi;
+ this.subscriptionApi = subscriptionApi;
}
@TimedResource
@@ -129,8 +149,8 @@ public class CatalogResource extends JaxRsResourceBase {
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
final DateTime catalogDateVersion = requestedDate != null ?
- DATE_TIME_FORMATTER.parseDateTime(requestedDate).toDateTime(DateTimeZone.UTC) :
- null;
+ DATE_TIME_FORMATTER.parseDateTime(requestedDate).toDateTime(DateTimeZone.UTC) :
+ null;
// Yack...
final VersionedCatalog catalog = (VersionedCatalog) catalogUserApi.getCatalog(catalogName, tenantContext);
@@ -196,6 +216,136 @@ public class CatalogResource extends JaxRsResourceBase {
return Response.status(Status.OK).entity(details).build();
}
+ @TimedResource
+ @GET
+ @Path("/plan")
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve plan for a given subscription and date", response = PlanJson.class)
+ @ApiResponses(value = {})
+ public Response getPlanForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+ @QueryParam("requestedDate") final String requestedDateString,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
+ verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+ final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+ if (lastEventBeforeRequestedDate == null) {
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is before the subscription start date", requestedDateString)).type("text/plain").build();
+ }
+
+ final Plan plan = lastEventBeforeRequestedDate.getNextPlan();
+ if (plan == null) {
+ // Subscription was cancelled at that point
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is after the subscription cancel date", requestedDateString)).type("text/plain").build();
+ }
+
+ final PlanJson planJson = new PlanJson(plan);
+ return Response.status(Status.OK).entity(planJson).build();
+ }
+
+ @TimedResource
+ @GET
+ @Path("/phase")
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve phase for a given subscription and date", response = PhaseJson.class)
+ @ApiResponses(value = {})
+ public Response getPhaseForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+ @QueryParam("requestedDate") final String requestedDateString,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException, CurrencyValueNull {
+ verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+ final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+ if (lastEventBeforeRequestedDate == null) {
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is before the subscription start date", requestedDateString)).type("text/plain").build();
+ }
+
+ final PlanPhase phase = lastEventBeforeRequestedDate.getNextPhase();
+ if (phase == null) {
+ // Subscription was cancelled at that point
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is after the subscription cancel date", requestedDateString)).type("text/plain").build();
+ }
+
+ final PhaseJson phaseJson = new PhaseJson(phase);
+ return Response.status(Status.OK).entity(phaseJson).build();
+ }
+
+ @TimedResource
+ @GET
+ @Path("/product")
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve product for a given subscription and date", response = ProductJson.class)
+ @ApiResponses(value = {})
+ public Response getProductForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+ @QueryParam("requestedDate") final String requestedDateString,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+ verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+ final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+ if (lastEventBeforeRequestedDate == null) {
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is before the subscription start date", requestedDateString)).type("text/plain").build();
+ }
+
+ final Product product = lastEventBeforeRequestedDate.getNextProduct();
+ if (product == null) {
+ // Subscription was cancelled at that point
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is after the subscription cancel date", requestedDateString)).type("text/plain").build();
+ }
+
+ final ProductJson productJson = new ProductJson(product);
+ return Response.status(Status.OK).entity(productJson).build();
+ }
+
+ @TimedResource
+ @GET
+ @Path("/priceList")
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve priceList for a given subscription and date", response = PriceListJson.class)
+ @ApiResponses(value = {})
+ public Response getPriceListForSubscriptionAndDate(@QueryParam("subscriptionId") final String subscriptionIdString,
+ @QueryParam("requestedDate") final String requestedDateString,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+ verifyNonNullOrEmpty(subscriptionIdString, "Subscription id needs to be specified");
+
+ final SubscriptionEvent lastEventBeforeRequestedDate = getLastEventBeforeDate(subscriptionIdString, requestedDateString, request);
+ if (lastEventBeforeRequestedDate == null) {
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is before the subscription start date", requestedDateString)).type("text/plain").build();
+ }
+
+ final PriceList priceList = lastEventBeforeRequestedDate.getNextPriceList();
+ if (priceList == null) {
+ // Subscription was cancelled at that point
+ return Response.status(Status.BAD_REQUEST).entity(String.format("%s is after the subscription cancel date", requestedDateString)).type("text/plain").build();
+ }
+
+ final PriceListJson priceListJson = new PriceListJson(priceList);
+ return Response.status(Status.OK).entity(priceListJson).build();
+ }
+
+ private SubscriptionEvent getLastEventBeforeDate(final String subscriptionIdString, final String requestedDateString, final HttpServletRequest request) throws SubscriptionApiException {
+ final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
+ final DateTime requestedDateTime = requestedDateString != null ?
+ DATE_TIME_FORMATTER.parseDateTime(requestedDateString).toDateTime(DateTimeZone.UTC) :
+ clock.getUTCNow();
+ final LocalDate requestedDate = requestedDateTime.toLocalDate();
+
+ final Subscription subscription = subscriptionApi.getSubscriptionForEntitlementId(UUID.fromString(subscriptionIdString), tenantContext);
+ SubscriptionEvent lastEventBeforeRequestedDate = null;
+ for (final SubscriptionEvent subscriptionEvent : subscription.getSubscriptionEvents()) {
+ if (lastEventBeforeRequestedDate == null) {
+ if (subscriptionEvent.getEffectiveDate().compareTo(requestedDate) > 0) {
+ // requestedDate too far in the past, before subscription start date
+ return null;
+ }
+ lastEventBeforeRequestedDate = subscriptionEvent;
+ }
+ if (subscriptionEvent.getEffectiveDate().compareTo(requestedDate) > 0) {
+ break;
+ } else {
+ lastEventBeforeRequestedDate = subscriptionEvent;
+ }
+ }
+
+ return lastEventBeforeRequestedDate;
+ }
@TimedResource
@POST
@@ -205,11 +355,11 @@ public class CatalogResource extends JaxRsResourceBase {
@ApiOperation(value = "Upload the full catalog as XML")
@ApiResponses(value = {})
public Response addSimplePlan(final SimplePlanJson simplePlan,
- @HeaderParam(HDR_CREATED_BY) final String createdBy,
- @HeaderParam(HDR_REASON) final String reason,
- @HeaderParam(HDR_COMMENT) final String comment,
- @javax.ws.rs.core.Context final HttpServletRequest request,
- @javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws Exception {
final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
final SimplePlanDescriptor desc = new DefaultSimplePlanDescriptor(simplePlan.getPlanId(),
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java
new file mode 100644
index 0000000..7235277
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.resources;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.entitlement.api.SubscriptionApi;
+import org.killbill.billing.jaxrs.json.CustomFieldJson;
+import org.killbill.billing.jaxrs.json.TagJson;
+import org.killbill.billing.jaxrs.util.Context;
+import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.util.api.AuditUserApi;
+import org.killbill.billing.util.api.CustomFieldApiException;
+import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.api.TagDefinitionApiException;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
+import org.killbill.commons.metrics.TimedResource;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path(JaxrsResource.INVOICES_ITEMS_PATH)
+@Api(value = JaxrsResource.INVOICES_ITEMS_PATH, description = "Operations on invoice items")
+public class InvoiceItemResource extends JaxRsResourceBase {
+ private static final String ID_PARAM_NAME = "invoiceItemId";
+
+ @Inject
+ public InvoiceItemResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi,
+ final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final PaymentApi paymentApi,
+ final SubscriptionApi subscriptionApi, final Clock clock, final Context context) {
+ super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, subscriptionApi, clock, context);
+ }
+
+ @TimedResource
+ @GET
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve invoice item custom fields", response = CustomFieldJson.class, responseContainer = "List")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied")})
+ public Response getCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @javax.ws.rs.core.Context final HttpServletRequest request) {
+ return super.getCustomFields(UUID.fromString(id), auditMode, context.createTenantContextNoAccountId(request));
+ }
+
+ @TimedResource
+ @POST
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Add custom fields to invoice item")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied")})
+ public Response createCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
+ return super.createCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request), uriInfo, request);
+ }
+
+
+ @TimedResource
+ @PUT
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Modify custom fields to invoice item")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied")})
+ public Response modifyCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ final List<CustomFieldJson> customFields,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws CustomFieldApiException {
+ return super.modifyCustomFields(UUID.fromString(id), customFields,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+
+ @TimedResource
+ @DELETE
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Remove custom fields from invoice item")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied")})
+ public Response deleteCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+ @QueryParam(QUERY_CUSTOM_FIELDS) final String customFieldList,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws CustomFieldApiException {
+ return super.deleteCustomFields(UUID.fromString(id), customFieldList,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+ @TimedResource
+ @GET
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + TAGS)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Retrieve invoice item tags", response = TagJson.class, responseContainer = "List")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied"),
+ @ApiResponse(code = 404, message = "Account not found")})
+ public Response getTags(@PathParam(ID_PARAM_NAME) final String id,
+ @QueryParam(QUERY_ACCOUNT_ID) final String accountIdStr,
+ @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+ @QueryParam(QUERY_TAGS_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException, AccountApiException {
+ final UUID accountId = UUID.fromString(accountIdStr);
+ final TenantContext tenantContext = context.createTenantContextWithAccountId(accountId, request);
+ final Account account = accountUserApi.getAccountById(accountId, tenantContext);
+
+ return super.getTags(account.getId(), UUID.fromString(id), auditMode, includedDeleted, tenantContext);
+ }
+
+ @TimedResource
+ @POST
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + TAGS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Add tags to invoice item")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied")})
+ public Response createTags(@PathParam(ID_PARAM_NAME) final String id,
+ @QueryParam(QUERY_TAGS) final String tagList,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final UriInfo uriInfo,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
+ return super.createTags(UUID.fromString(id), tagList, uriInfo,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request), request);
+ }
+
+ @TimedResource
+ @DELETE
+ @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + TAGS)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Remove tags from invoice item")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice item id supplied")})
+ public Response deleteTags(@PathParam(ID_PARAM_NAME) final String id,
+ @QueryParam(QUERY_TAGS) final String tagList,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
+ return super.deleteTags(UUID.fromString(id), tagList,
+ context.createCallContextNoAccountId(createdBy, reason, comment, request));
+ }
+
+ @Override
+ protected ObjectType getObjectType() {
+ return ObjectType.INVOICE_ITEM;
+ }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 2bb186c..df9215f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -576,7 +576,10 @@ public class InvoiceResource extends JaxRsResourceBase {
input.getStartDate(),
input.getEndDate(),
input.getAmount(),
+ input.getRate(),
accountCurrency.name(),
+ input.getQuantity(),
+ input.getItemDetails(),
null,
null);
}
@@ -1007,6 +1010,27 @@ public class InvoiceResource extends JaxRsResourceBase {
return Response.status(Response.Status.OK).build();
}
+ @TimedResource
+ @PUT
+ @Path("/{invoiceId:" + UUID_PATTERN + "}/" + VOID_INVOICE)
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Perform the action of voiding an invoice")
+ @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid invoice id supplied"),
+ @ApiResponse(code = 404, message = "Invoice not found")})
+ public Response voidInvoice(@PathParam("invoiceId") final String invoiceIdString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws InvoiceApiException {
+
+ final CallContext callContext = context.createCallContextNoAccountId(createdBy, reason, comment, request);
+ final UUID invoiceId = UUID.fromString(invoiceIdString);
+ invoiceApi.voidInvoice(invoiceId, callContext);
+ return Response.status(Response.Status.OK).build();
+ }
+
@Override
protected ObjectType getObjectType() {
return ObjectType.INVOICE;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 7923488..6264557 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -107,6 +107,7 @@ public interface JaxrsResource {
public static final String QUERY_INVOICE_WITH_ITEMS = "withItems";
public static final String QUERY_WITH_MIGRATION_INVOICES = "withMigrationInvoices";
public static final String QUERY_UNPAID_INVOICES_ONLY = "unpaidInvoicesOnly";
+ public static final String QUERY_INCLUDE_VOIDED_INVOICES = "includeVoidedInvoices";
public static final String QUERY_INVOICE_WITH_CHILDREN_ITEMS = "withChildrenItems";
public static final String QUERY_PAYMENT_EXTERNAL = "externalPayment";
@@ -188,6 +189,9 @@ public interface JaxrsResource {
public static final String INVOICES = "invoices";
public static final String INVOICES_PATH = PREFIX + "/" + INVOICES;
+ public static final String INVOICE_ITEMS = "invoiceItems";
+ public static final String INVOICES_ITEMS_PATH = PREFIX + "/" + INVOICE_ITEMS;
+
public static final String CHARGES = "charges";
public static final String CHARGES_PATH = PREFIX + "/" + INVOICES + "/" + CHARGES;
@@ -284,6 +288,7 @@ public interface JaxrsResource {
public static final String INVOICE_TRANSLATION = "translation";
public static final String INVOICE_CATALOG_TRANSLATION = "catalogTranslation";
public static final String COMMIT_INVOICE = "commitInvoice";
+ public static final String VOID_INVOICE = "voidInvoice";
public static final String COMBO = "combo";
public static final String MIGRATION = "migration";
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 0fc62db..f0490db 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,7 @@
package org.killbill.billing.jaxrs.resources;
+import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
@@ -349,9 +350,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
generator.close();
} finally {
// In case the client goes away (IOException), make sure to close the underlying DB connection
- while (iterator.hasNext()) {
- iterator.next();
- }
+ entities.close();
}
}
};
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
index a73a9b9..5e01d3c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,12 +18,15 @@
package org.killbill.billing.jaxrs.resources;
+import java.util.Iterator;
+
import javax.inject.Inject;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -253,12 +256,21 @@ public class TestResource extends JaxRsResourceBase {
private boolean areAllNotificationsProcessed(final Long tenantRecordId) {
int nbNotifications = 0;
- for (final NotificationQueue notificationQueue : notificationQueueService.getNotificationQueues()) {
- for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(null, tenantRecordId)) {
- if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
- nbNotifications += 1;
+ final Iterator<NotificationQueue> iterator = notificationQueueService.getNotificationQueues().iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationQueue notificationQueue = iterator.next();
+ for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueue.getFutureOrInProcessingNotificationForSearchKey2(null, tenantRecordId)) {
+ if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+ nbNotifications += 1;
+ }
}
}
+ } finally {
+ // Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
if (nbNotifications != 0) {
log.info("TestResource: {} queue(s) with more notification(s) to process", nbNotifications);
@@ -268,6 +280,7 @@ public class TestResource extends JaxRsResourceBase {
private boolean areAllBusEventsProcessed(final Long tenantRecordId) {
final Iterable<BusEventWithMetadata<BusEvent>> availableBusEventForSearchKey2 = persistentBus.getAvailableOrInProcessingBusEventsForSearchKey2(null, tenantRecordId);
+ // This will go through all results to close the connection
final int nbBusEvents = Iterables.size(availableBusEventForSearchKey2);
if (nbBusEvents != 0) {
log.info("TestResource: at least {} more bus event(s) to process", nbBusEvents);
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
index 42f51ba..f61257d 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
@@ -56,7 +56,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuiteNoDB {
bundleId, subscriptionId, planName, phaseName, usageName,
null, null, null,
type, description,
- startDate, endDate, amount, currency.name(), null, auditLogs);
+ startDate, endDate, amount, null, currency.name(), null, null, null, auditLogs);
Assert.assertEquals(invoiceItemJson.getInvoiceItemId(), invoiceItemId);
Assert.assertEquals(invoiceItemJson.getInvoiceId(), invoiceId);
Assert.assertEquals(invoiceItemJson.getLinkedInvoiceItemId(), linkedInvoiceItemId);
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index b3f3200..17d22cd 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
NEWS 31(+31 -0)
diff --git a/NEWS b/NEWS
index 9b7028a..5350c99 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,34 @@
+0.19.3
+ Fix issue with tag idempotency. Fixes #857
+ Fix JDBC connection leak in pagination API. Fixes #853
+ Fix limitation where catalog plan name cannot end with an number. Fixes #842
+ Reduce log level of InvoiceItemGeneratorLogger. Fixes #851
+ Usage detail/aggregate mode. Fixes #839
+ Add metadata detail field to external charges. Fixes #843
+ Add ability to void invoices. Fixes #833
+
+0.19.2
+ Fix issue with STANDALONE plans (#840)
+ Fix connection leak (#558)
+ Fix limitation where catalog plan name cannot end with an number (#842)
+ Fix missing Invoice Notification when we have future billing events (#846)
+ Reduce log level of InvoiceItemGeneratorLogger (#851)
+
+0.18.18
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.18
+
+0.18.17
+ Relax sanity checks for STANDALONE subscriptions #840
+ Fix JDBC connection leak in pagination API #853
+ Fix limitation where catalog plan name cannot end with an number #842
+ Reduce log level of InvoiceItemGeneratorLogger #851
+
+0.18.16
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.16
+
+0.18.15
+ See https://github.com/killbill/killbill/releases/tag/killbill-0.18.15
+
0.18.14
See https://github.com/killbill/killbill/releases/tag/killbill-0.18.14
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index 290cd8a..88191cf 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
index d06c167..fc14f47 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,7 @@
package org.killbill.billing.overdue.notification;
+import java.util.Iterator;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -93,10 +94,18 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final Iterable<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, checkOverdueQueue,
clazz, context);
- for (final NotificationEventWithMetadata<T> notification : futureNotifications) {
- checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), notification.getRecordId());
+ final Iterator<NotificationEventWithMetadata<T>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<T> notification = iterator.next();
+ checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), notification.getRecordId());
+ }
+ } finally {
+ // Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
-
return null;
}
});
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
index 25d7eff..3fc227a 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,8 @@
package org.killbill.billing.overdue.notification;
+import java.util.Iterator;
+
import org.joda.time.DateTime;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
@@ -48,23 +50,32 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
boolean shouldInsertNewNotification = true;
int minIndexToDeleteFrom = 0;
int index = 0;
- for (final NotificationEventWithMetadata<T> cur : futureNotifications) {
- // Results are ordered by effective date asc
- if (index == 0) {
- if (cur.getEffectiveDate().isBefore(futureNotificationTime)) {
- // We don't have to insert a new one. For sanity, delete any other future notification
- minIndexToDeleteFrom = 1;
- shouldInsertNewNotification = false;
- } else {
- // We win - we are before any other already recorded. Delete all others.
- minIndexToDeleteFrom = 0;
+ final Iterator<NotificationEventWithMetadata<T>> iterator = futureNotifications.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<T> cur = iterator.next();
+ // Results are ordered by effective date asc
+ if (index == 0) {
+ if (cur.getEffectiveDate().isBefore(futureNotificationTime)) {
+ // We don't have to insert a new one. For sanity, delete any other future notification
+ minIndexToDeleteFrom = 1;
+ shouldInsertNewNotification = false;
+ } else {
+ // We win - we are before any other already recorded. Delete all others.
+ minIndexToDeleteFrom = 0;
+ }
}
- }
- if (minIndexToDeleteFrom <= index) {
- overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), cur.getRecordId());
+ if (minIndexToDeleteFrom <= index) {
+ overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getHandle().getConnection(), cur.getRecordId());
+ }
+ index++;
+ }
+ } finally {
+ // Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
}
- index++;
}
return shouldInsertNewNotification;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
index 1a7b3bd..ca991a2 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
@@ -92,6 +92,7 @@ public class TestDefaultOverdueCheckPoster extends OverdueTestSuiteWithEmbeddedD
return entitySqlDaoTransactionalJdbiWrapper.execute(new EntitySqlDaoTransactionWrapper<List<NotificationEventWithMetadata<OverdueCheckNotificationKey>>>() {
@Override
public List<NotificationEventWithMetadata<OverdueCheckNotificationKey>> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ // This will go through all results to close the connection
return ImmutableList.<NotificationEventWithMetadata<OverdueCheckNotificationKey>>copyOf(((OverdueCheckPoster) checkPoster).getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue, OverdueCheckNotificationKey.class, internalCallContext));
}
});
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 1a79089..1b75542 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index 18d3493..b8bcf61 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -680,11 +680,6 @@ public class PaymentMethodProcessor extends ProcessorBase {
private void updateDefaultPaymentMethodIfNeeded(final String pluginName, final Account account, @Nullable final UUID defaultPluginPaymentMethodId, final InternalCallContext context) throws PaymentApiException, AccountApiException {
- // If the plugin does not have a default payment gateway, we keep the current default payment method in KB account as it is.
- if (defaultPluginPaymentMethodId == null) {
- return;
- }
-
// Some gateways have the concept of default payment methods. Kill Bill has also its own default payment method
// and is authoritative on this matter. However, if the default payment method is associated with a given plugin,
// and if the default payment method in that plugin has changed, we will reflect this change in Kill Bill as well.
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 4bd018a..ecaa6d8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -411,11 +412,19 @@ public class PaymentProcessor extends ProcessorBase {
final Iterable<NotificationEventWithMetadata<NotificationEvent>> notificationEventWithMetadatas =
retryQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
- for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationEventWithMetadatas) {
- if (((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId().equals(lastPaymentAttemptId)) {
- retryQueue.removeNotification(notificationEvent.getRecordId());
+ final Iterator<NotificationEventWithMetadata<NotificationEvent>> iterator = notificationEventWithMetadatas.iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<NotificationEvent> notificationEvent = iterator.next();
+ if (((PaymentRetryNotificationKey) notificationEvent.getEvent()).getAttemptId().equals(lastPaymentAttemptId)) {
+ retryQueue.removeNotification(notificationEvent.getRecordId());
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
} catch (final NoSuchNotificationQueue noSuchNotificationQueue) {
log.error("ERROR Loading Notification Queue - " + noSuchNotificationQueue.getMessage());
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index 8e0f0fe..27a8bde 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -26,6 +26,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
import org.killbill.billing.payment.api.Payment;
@@ -94,7 +95,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(retrievedAttempts.get(0).getPluginName(), pluginName);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPaymentAndTransactions() {
final UUID paymentMethodId = UUID.randomUUID();
final UUID accountId = UUID.randomUUID();
@@ -293,7 +295,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(deletedPaymentMethod.getPluginName(), pluginName);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPendingTransactions() {
final UUID paymentMethodId = UUID.randomUUID();
diff --git a/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java b/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java
index fa16ee5..1627a6d 100644
--- a/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java
+++ b/payment/src/test/java/org/killbill/billing/payment/MockRecurringInvoiceItem.java
@@ -42,6 +42,8 @@ public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem
protected final BigDecimal amount;
protected final Currency currency;
protected final String usageName;
+ protected final Integer quantity;
+ protected final String itemDetails;
public MockRecurringInvoiceItem(final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
final String planName, final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate,
@@ -52,12 +54,12 @@ public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem
public MockRecurringInvoiceItem(final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId, final String planName, final String phaseName, final String usageName,
final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, final BigDecimal rate, final UUID reversedItemId) {
this(UUID.randomUUID(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName,
- startDate, endDate, amount, currency, rate, reversedItemId);
+ startDate, endDate, amount, currency, rate, reversedItemId, null, null);
}
public MockRecurringInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId, final String planName, final String phaseName,
final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency,
- final BigDecimal rate, final UUID reversedItemId) {
+ final BigDecimal rate, final UUID reversedItemId, final Integer quantity, final String itemDetails) {
super(id);
this.invoiceId = invoiceId;
this.accountId = accountId;
@@ -72,6 +74,8 @@ public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem
this.currency = currency;
this.rate = rate;
this.reversedItemId = reversedItemId;
+ this.quantity = quantity;
+ this.itemDetails = itemDetails;
}
@Override
@@ -165,6 +169,12 @@ public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem
}
@Override
+ public Integer getQuantity() { return quantity; }
+
+ @Override
+ public String getItemDetails() { return itemDetails; }
+
+ @Override
public boolean matches(final Object other) {
throw new UnsupportedOperationException();
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index d09c923..ecaecf1 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -20,6 +20,7 @@ package org.killbill.billing.payment;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -28,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.callcontext.InternalCallContext;
@@ -303,7 +305,8 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testUnknownEntriesWithFailures() throws PaymentApiException, EventBusException {
final BigDecimal requestedAmount = BigDecimal.TEN;
@@ -414,7 +417,8 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
}
// The test will check that when a PENDING entry stays PENDING, we go through all our retries and eventually give up (no infinite loop of retries)
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPendingEntriesThatDontMove() throws Exception {
final BigDecimal requestedAmount = BigDecimal.TEN;
@@ -447,13 +451,13 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
// Verify there is a notification to retry updating the value
assertEquals(getPendingNotificationCnt(internalCallContext), 1);
- clock.addDeltaFromReality(cur.getMillis() + 1);
+ clock.addDeltaFromReality(cur.getMillis() + 1000);
assertNotificationsCompleted(internalCallContext, 5);
// We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted alone would not work
// because there is a point in time where the notification queue is empty (showing notification was processed), but the processing of the notification
// will itself enter a new notification, and so the synchronization is difficult without writing *too much code*.
- Thread.sleep(1000);
+ Thread.sleep(1500);
assertNotificationsCompleted(internalCallContext, 5);
final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
@@ -510,11 +514,19 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
@Override
public Boolean call() throws Exception {
boolean completed = true;
- for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())) {
- if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
- completed = false;
+ final Iterator<NotificationEventWithMetadata<NotificationEvent>> iterator = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()).iterator();
+ try {
+ while (iterator.hasNext()) {
+ final NotificationEventWithMetadata<NotificationEvent> notificationEvent = iterator.next();
+ if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+ completed = false;
+ }
}
+ } finally {
// Go through all results to close the connection
+ while (iterator.hasNext()) {
+ iterator.next();
+ }
}
return completed;
}
pom.xml 4(+2 -2)
diff --git a/pom.xml b/pom.xml
index 18444f3..ba61673 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,10 +21,10 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.141.19</version>
+ <version>0.141.36</version>
</parent>
<artifactId>killbill</artifactId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<packaging>pom</packaging>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
profiles/killbill/pom.xml 2(+1 -1)
diff --git a/profiles/killbill/pom.xml b/profiles/killbill/pom.xml
index e5b514a..4ddc355 100644
--- a/profiles/killbill/pom.xml
+++ b/profiles/killbill/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killbill</artifactId>
diff --git a/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java b/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java
index 14c5eda..3595863 100644
--- a/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java
+++ b/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2015 Groupon, Inc
- * Copyright 2015 The Billing Project, LLC
+ * Copyright 2015-2018 Groupon, Inc
+ * Copyright 2015-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,7 @@
package org.apache.shiro.authc.pam;
import java.util.Collection;
+import java.util.LinkedList;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
@@ -26,13 +27,19 @@ import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-// See https://issues.apache.org/jira/browse/SHIRO-540
+/**
+ * Fix for https://issues.apache.org/jira/browse/SHIRO-540
+ * Support for additional realms non injected
+ */
public class ModularRealmAuthenticatorWith540 extends ModularRealmAuthenticator {
private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);
- public ModularRealmAuthenticatorWith540(final ModularRealmAuthenticator delegate) {
- setRealms(delegate.getRealms());
+ public ModularRealmAuthenticatorWith540(final Collection<Realm> realmsFromShiroIni, final ModularRealmAuthenticator delegate) {
+ // Note: order matters (the first successful match will win)
+ final Collection<Realm> realms = new LinkedList<Realm>(realmsFromShiroIni);
+ realms.addAll(delegate.getRealms());
+ setRealms(realms);
setAuthenticationStrategy(delegate.getAuthenticationStrategy());
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
index d79edbe..d393cb8 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
@@ -53,7 +53,8 @@ public class CleanupListener implements ServletContextListener {
// See https://mariadb.atlassian.net/browse/CONJ-61
try {
Class.forName("org.mariadb.jdbc.Driver");
- org.mariadb.jdbc.Driver.unloadDriver();
+ // Removed by https://github.com/MariaDB/mariadb-connector-j/commit/ff91ae0bb4f5c49beaba7475b76883b426a51cd4#diff-7d2a758f3b306f512cd12ad68eeb0137
+ //org.mariadb.jdbc.Driver.unloadDriver();
} catch (final ClassNotFoundException ignored) {
// MariaDB driver not used
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
index 92668d3..b182e9e 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
@@ -34,6 +34,7 @@ import org.killbill.billing.jaxrs.resources.CatalogResource;
import org.killbill.billing.jaxrs.resources.CreditResource;
import org.killbill.billing.jaxrs.resources.CustomFieldResource;
import org.killbill.billing.jaxrs.resources.ExportResource;
+import org.killbill.billing.jaxrs.resources.InvoiceItemResource;
import org.killbill.billing.jaxrs.resources.InvoicePaymentResource;
import org.killbill.billing.jaxrs.resources.InvoiceResource;
import org.killbill.billing.jaxrs.resources.NodesInfoResource;
@@ -208,6 +209,7 @@ public class KillbillServerModule extends KillbillPlatformModule {
bind(TenantResource.class).asEagerSingleton();
bind(TransactionResource.class).asEagerSingleton();
bind(UsageResource.class).asEagerSingleton();
+ bind(InvoiceItemResource.class).asEagerSingleton();
bind(KillbillEventHandler.class).asEagerSingleton();
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index eaf9e55..d7ce566 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,9 @@
package org.killbill.billing.server.modules;
+import java.util.Collection;
+import java.util.LinkedList;
+
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -25,8 +28,10 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authc.pam.ModularRealmAuthenticatorWith540;
+import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.guice.web.ShiroWebModuleWith435;
+import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
@@ -38,9 +43,9 @@ import org.killbill.billing.server.security.KillBillWebSessionManager;
import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
import org.killbill.billing.util.config.definition.RbacConfig;
import org.killbill.billing.util.glue.EhcacheShiroManagerProvider;
-import org.killbill.billing.util.glue.IniRealmProvider;
import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
import org.killbill.billing.util.glue.KillBillShiroModule;
+import org.killbill.billing.util.glue.RealmsFromShiroIniProvider;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
@@ -82,8 +87,6 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
final RbacConfig config = new ConfigurationObjectFactory(configSource).build(RbacConfig.class);
bind(RbacConfig.class).toInstance(config);
- // Note: order matters (the first successful match will win, see below)
- bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
bindRealm().to(KillBillJdbcRealm.class).asEagerSingleton();
if (KillBillShiroModule.isLDAPEnabled()) {
bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
@@ -133,7 +136,7 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
}
}
- private static final class DefaultWebSecurityManagerTypeListener implements TypeListener {
+ private final class DefaultWebSecurityManagerTypeListener implements TypeListener {
@Override
public <I> void hear(final TypeLiteral<I> typeLiteral, final TypeEncounter<I> typeEncounter) {
@@ -141,10 +144,21 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
@Override
public void afterInjection(final Object o) {
final DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) o;
+
+ // Other realms have been injected by Guice (bindRealm().toInstance(...) makes Guice throw a ClassCastException?!)
+ final Collection<Realm> realmsFromShiroIni = RealmsFromShiroIniProvider.get(configSource);
+
+ if (webSecurityManager.getAuthorizer() instanceof ModularRealmAuthorizer) {
+ final ModularRealmAuthorizer modularRealmAuthorizer = (ModularRealmAuthorizer) webSecurityManager.getAuthorizer();
+ final Collection<Realm> realms = new LinkedList<Realm>(realmsFromShiroIni);
+ realms.addAll(modularRealmAuthorizer.getRealms());
+ modularRealmAuthorizer.setRealms(realms);
+ }
+
if (webSecurityManager.getAuthenticator() instanceof ModularRealmAuthenticator) {
final ModularRealmAuthenticator authenticator = (ModularRealmAuthenticator) webSecurityManager.getAuthenticator();
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategyWith540());
- webSecurityManager.setAuthenticator(new ModularRealmAuthenticatorWith540(authenticator));
+ webSecurityManager.setAuthenticator(new ModularRealmAuthenticatorWith540(realmsFromShiroIni, authenticator));
}
}
});
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index cd4b396..ffdbd07 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -151,7 +151,7 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
input.setBillingPeriod(billingPeriod);
input.setPriceList(PriceListSet.DEFAULT_PRICELIST_NAME);
- return killBillClient.createSubscription(input, null, waitCompletion ? DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC : -1, requestOptions);
+ return killBillClient.createSubscription(input, null, null, waitCompletion ? DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC : -1, false, requestOptions);
}
protected Account createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice() throws Exception {
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java
index 4a28ea3..26e2a9c 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAdmin.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.client.JaxrsResource;
@@ -54,7 +55,8 @@ import static org.testng.Assert.assertNotNull;
public class TestAdmin extends TestJaxrsBase {
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testAdminPaymentEndpoint() throws Exception {
final Account account = createAccountWithDefaultPaymentMethod();
@@ -117,7 +119,7 @@ public class TestAdmin extends TestJaxrsBase {
crappyWaitForLackOfProperSynchonization();
Assert.assertEquals(killBillClient.getInvoices(requestOptions).getPaginationMaxNbRecords(), i + 1);
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), false, false, false, AuditLevel.NONE, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), false, false, false, false, AuditLevel.NONE, requestOptions);
assertEquals(invoices.size(), 1);
}
@@ -127,7 +129,7 @@ public class TestAdmin extends TestJaxrsBase {
Assert.assertEquals(killBillClient.getInvoices(requestOptions).getPaginationMaxNbRecords(), 10);
for (final UUID accountId : accounts) {
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountId, false, false, false, AuditLevel.NONE, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountId, false, false, false, false, AuditLevel.NONE, requestOptions);
assertEquals(invoices.size(), 2);
}
@@ -145,7 +147,7 @@ public class TestAdmin extends TestJaxrsBase {
Assert.assertEquals(killBillClient.getInvoices(requestOptions).getPaginationMaxNbRecords(), 10);
for (final UUID accountId : accounts) {
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountId, false, false, false, AuditLevel.NONE, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountId, false, false, false, false, AuditLevel.NONE, requestOptions);
assertEquals(invoices.size(), 2);
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
index c00915a..ee38da4 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCache.java
@@ -1,5 +1,6 @@
/*
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -21,6 +22,7 @@ import java.util.UUID;
import org.joda.time.LocalDate;
import org.killbill.automaton.StateMachineConfig;
import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.PriceListSet;
@@ -100,7 +102,8 @@ public class TestCache extends TestJaxrsBase {
Assert.assertFalse(accountBcdCache.isKeyInCache(input.getAccountId()));
}
- @Test(groups = "slow", description = "Can Invalidate (clear) all Tenant Caches for current Tenant")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", description = "Can Invalidate (clear) all Tenant Caches for current Tenant", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testInvalidateCacheByTenant() throws Exception {
// creating a new Tenant for this test
final String testApiKey = "testApiKey";
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
index a0f6ad4..c5f263b 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestCatalog.java
@@ -106,7 +106,7 @@ public class TestCatalog extends TestJaxrsBase {
Assert.assertEquals(catalogsJson.get(0).getName(), "Firearms");
Assert.assertEquals(catalogsJson.get(0).getEffectiveDate(), Date.valueOf("2011-01-01"));
Assert.assertEquals(catalogsJson.get(0).getCurrencies().size(), 3);
- Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 11);
+ Assert.assertEquals(catalogsJson.get(0).getProducts().size(), 12);
Assert.assertEquals(catalogsJson.get(0).getPriceLists().size(), 7);
for (final Product productJson : catalogsJson.get(0).getProducts()) {
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
index 94150fa..899ca00 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestEntitlement.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.catalog.DefaultPriceListSet;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -277,7 +278,7 @@ public class TestEntitlement extends TestJaxrsBase {
Assert.assertEquals(subscription.getEvents().get(2).getProduct(), "Shotgun");
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, false, AuditLevel.FULL, requestOptions);
assertEquals(invoices.size(), 1);
assertEquals(invoices.get(0).getAmount().compareTo(BigDecimal.TEN), 0);
@@ -392,7 +393,8 @@ public class TestEntitlement extends TestJaxrsBase {
assertEquals(killBillClient.getInvoiceTags(invoicesAfterClose.get(0).getInvoiceId(), requestOptions).size(), 1);
}
- @Test(groups = "slow", description = "Create a bulk of base entitlement and addOns under the same transaction")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", description = "Create a bulk of base entitlement and addOns under the same transaction", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testCreateEntitlementsWithAddOnsThenCloseAccountWithItemAdjustment() throws Exception {
final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
@@ -541,7 +543,7 @@ public class TestEntitlement extends TestJaxrsBase {
assertEquals(bundles.size(), 1);
assertEquals(bundles.get(0).getSubscriptions().size(), 3);
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, false, AuditLevel.FULL, requestOptions);
assertEquals(invoices.size(), 2);
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
index e1d9f5c..29ccf26 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoice.java
@@ -70,7 +70,7 @@ public class TestInvoice extends TestJaxrsBase {
final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
- final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.FULL, requestOptions);
+ final List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, false, AuditLevel.FULL, requestOptions);
assertEquals(invoices.size(), 2);
for (final Invoice invoiceJson : invoices) {
Assert.assertEquals(invoiceJson.getAuditLogs().size(), 1);
@@ -447,6 +447,7 @@ public class TestInvoice extends TestJaxrsBase {
externalCharge.setAmount(chargeAmount);
externalCharge.setCurrency(accountJson.getCurrency());
externalCharge.setDescription(UUID.randomUUID().toString());
+ externalCharge.setItemDetails("Item Details");
final LocalDate startDate = clock.getUTCToday();
externalCharge.setStartDate(startDate);
@@ -462,6 +463,7 @@ public class TestInvoice extends TestJaxrsBase {
assertNull(invoiceWithItems.getItems().get(0).getBundleId());
assertEquals(invoiceWithItems.getItems().get(0).getStartDate().compareTo(startDate), 0);
assertEquals(invoiceWithItems.getItems().get(0).getEndDate().compareTo(endDate), 0);
+ assertEquals(invoiceWithItems.getItems().get(0).getItemDetails(), "Item Details");
// Verify the total number of invoices
assertEquals(killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions).size(), 3);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoiceItem.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoiceItem.java
new file mode 100644
index 0000000..a846d1b
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoiceItem.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.killbill.billing.GuicyKillbillTestSuite;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.client.RequestOptions;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.AuditLog;
+import org.killbill.billing.client.model.CustomField;
+import org.killbill.billing.client.model.InvoiceItem;
+import org.killbill.billing.client.model.Invoices;
+import org.killbill.billing.client.model.Tag;
+import org.killbill.billing.client.model.TagDefinition;
+import org.killbill.billing.jaxrs.resources.JaxrsResource;
+import org.killbill.billing.util.api.AuditLevel;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+
+public class TestInvoiceItem extends TestJaxrsBase {
+
+ @Test(groups = "slow", description = "Add tags to invoice item")
+ public void testTags() throws Exception {
+ final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+ final Invoices invoicesJson = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+
+ Assert.assertNotNull(invoicesJson);
+ Assert.assertEquals(invoicesJson.size(), 2);
+
+ final List<InvoiceItem> invoiceItems = invoicesJson.get(0).getItems();
+
+ Assert.assertNotNull(invoiceItems);
+
+ // Create tag definition
+ final TagDefinition input = new TagDefinition(null, false, "tagtest", "invoice item tag test", ImmutableList.<ObjectType>of(ObjectType.INVOICE_ITEM));
+
+ final TagDefinition objFromJson = killBillClient.createTagDefinition(input, requestOptions);
+ Assert.assertNotNull(objFromJson);
+ Assert.assertEquals(objFromJson.getName(), input.getName());
+ Assert.assertEquals(objFromJson.getDescription(), input.getDescription());
+
+ // Add a tag
+ final Multimap<String, String> followQueryParams = HashMultimap.create();
+ followQueryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId().toString());
+ final RequestOptions followRequestOptions = requestOptions.extend().withQueryParamsForFollow(followQueryParams).build();
+ killBillClient.createInvoiceItemTag(invoiceItems.get(0).getInvoiceItemId(),objFromJson.getId(), followRequestOptions);
+
+ // Add account id to request
+ final Multimap<String, String> queryParams = HashMultimap.create();
+ queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId().toString());
+ final RequestOptions addedRequestOptions = requestOptions.extend().withQueryParams(queryParams).build();
+
+ // Retrieves all tags
+ final List<Tag> tags1 = killBillClient.getInvoiceItemTags(invoiceItems.get(0).getInvoiceItemId(), AuditLevel.FULL, addedRequestOptions);
+ Assert.assertEquals(tags1.size(), 1);
+ Assert.assertEquals(tags1.get(0).getTagDefinitionId(), objFromJson.getId());
+
+ // Verify adding the same tag a second time doesn't do anything
+ killBillClient.createInvoiceItemTag(invoiceItems.get(0).getInvoiceItemId(), objFromJson.getId(), followRequestOptions);
+
+ // Retrieves all tags again
+ final List<Tag> tags2 = killBillClient.getInvoiceItemTags(invoiceItems.get(0).getInvoiceItemId(), AuditLevel.FULL, addedRequestOptions);
+ Assert.assertEquals(tags2, tags1);
+
+ // Verify audit logs
+ Assert.assertEquals(tags2.get(0).getAuditLogs().size(), 1);
+ final AuditLog auditLogJson = tags2.get(0).getAuditLogs().get(0);
+ Assert.assertEquals(auditLogJson.getChangeType(), "INSERT");
+ Assert.assertEquals(auditLogJson.getChangedBy(), createdBy);
+ Assert.assertEquals(auditLogJson.getReasonCode(), reason);
+ Assert.assertEquals(auditLogJson.getComments(), comment);
+ Assert.assertNotNull(auditLogJson.getChangeDate());
+ Assert.assertNotNull(auditLogJson.getUserToken());
+
+ // remove it
+ killBillClient.deleteInvoiceItemTag(invoiceItems.get(0).getInvoiceItemId(), objFromJson.getId(), requestOptions);
+ final List<Tag> tags3 = killBillClient.getInvoiceItemTags(invoiceItems.get(0).getInvoiceItemId(), AuditLevel.FULL, addedRequestOptions);
+ Assert.assertEquals(tags3.size(), 0);
+
+ killBillClient.deleteTagDefinition(objFromJson.getId(),requestOptions);
+ List<TagDefinition> objsFromJson = killBillClient.getTagDefinitions(requestOptions);
+ Assert.assertNotNull(objsFromJson);
+ Boolean isFound = false;
+ for (TagDefinition tagDefinition : objsFromJson){
+ isFound |= tagDefinition.getId().equals(objFromJson.getId());
+ }
+ Assert.assertFalse(isFound);
+ }
+
+ @Test(groups = "slow", description = "Add custom fields to invoice item")
+ public void testCustomFields() throws Exception {
+ final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+ final Invoices invoicesJson = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+
+ Assert.assertNotNull(invoicesJson);
+ Assert.assertEquals(invoicesJson.size(), 2);
+
+ final List<InvoiceItem> invoiceItems = invoicesJson.get(0).getItems();
+
+ Assert.assertNotNull(invoiceItems);
+
+ final Collection<CustomField> customFields = new LinkedList<CustomField>();
+ customFields.add(new CustomField(null, invoiceItems.get(0).getInvoiceItemId(), ObjectType.INVOICE_ITEM, "1", "value1", null));
+ customFields.add(new CustomField(null, invoiceItems.get(0).getInvoiceItemId(), ObjectType.INVOICE_ITEM, "2", "value2", null));
+ customFields.add(new CustomField(null, invoiceItems.get(0).getInvoiceItemId(), ObjectType.INVOICE_ITEM, "3", "value3", null));
+
+ killBillClient.createInvoiceItemCustomField(invoiceItems.get(0).getInvoiceItemId(), customFields, requestOptions);
+
+ final List<CustomField> invoiceItemCustomFields = killBillClient.getInvoiceItemCustomFields(invoiceItems.get(0).getInvoiceItemId(), requestOptions);
+ Assert.assertEquals(invoiceItemCustomFields.size(), 3);
+
+ // Delete all custom fields for account
+ killBillClient.deleteInvoiceItemCustomFields(invoiceItems.get(0).getInvoiceItemId(), requestOptions);
+
+ final List<CustomField> remainingCustomFields = killBillClient.getInvoiceItemCustomFields(invoiceItems.get(0).getInvoiceItemId(), requestOptions);
+ Assert.assertEquals(remainingCustomFields.size(), 0);
+ }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoiceVoid.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoiceVoid.java
new file mode 100644
index 0000000..ed56795
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestInvoiceVoid.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.Invoice;
+import org.killbill.billing.client.model.InvoicePayment;
+import org.killbill.billing.client.model.InvoicePaymentTransaction;
+import org.killbill.billing.client.model.InvoicePayments;
+import org.killbill.billing.client.model.PaymentTransaction;
+import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.util.api.AuditLevel;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestInvoiceVoid extends TestJaxrsBase {
+
+ @Test(groups = "slow", description = "Can void an invoice")
+ public void testInvoiceVoid() throws Exception {
+ final Account accountJson = createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice();
+ assertNotNull(accountJson);
+
+ // Verify we didn't get any invoicePayment
+ final List<InvoicePayment> noPaymentsFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ assertEquals(noPaymentsFromJson.size(), 0);
+
+ // Get the invoices
+ List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ // 2 invoices but look for the non zero dollar one
+ assertEquals(invoices.size(), 2);
+ // verify account balance
+ Account account = killBillClient.getAccount(accountJson.getAccountId(), true, true, requestOptions);
+ assertEquals(account.getAccountBalance().compareTo(invoices.get(1).getBalance()), 0);
+
+ // void the invoice
+ killBillClient.voidInvoice(invoices.get(1).getInvoiceId(), requestOptions);
+
+ // Get the invoices excluding voided
+ invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ // the voided invoice should not be returned
+ assertEquals(invoices.size(), 1);
+
+ // Get the invoices including voided
+ invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, true, true, AuditLevel.NONE, requestOptions);
+ assertEquals(invoices.size(), 2);
+ assertEquals(invoices.get(1).getStatus(), InvoiceStatus.VOID.toString());
+ assertEquals(invoices.get(1).getBalance().compareTo(BigDecimal.ZERO), 0);
+
+ // check that account balance is zero
+ account = killBillClient.getAccount(accountJson.getAccountId(), true, true, requestOptions);
+ assertEquals(account.getAccountBalance().compareTo(BigDecimal.ZERO), 0);
+
+ // After invoice was voided verify the subscription is re-invoiced on a new invoice
+ // trigger an invoice generation
+ killBillClient.createInvoice(accountJson.getAccountId(), clock.getToday(DateTimeZone.forID(accountJson.getTimeZone())), requestOptions);
+
+ // Get the invoices excluding voided
+ invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ // the voided invoice should not be returned
+ assertEquals(invoices.size(), 2);
+
+ // process payment
+ InvoicePayment invoicePayment = processPayment(accountJson, invoices.get(1), false);
+
+ // try to void invoice
+ try {
+ killBillClient.voidInvoice(invoices.get(1).getInvoiceId(), requestOptions);
+ Assert.fail("VoidInvoice call should fail with 400");
+ } catch (final KillBillClientException e) {
+ assertTrue(true);
+ }
+
+ //refund payment
+ refundPayment(invoicePayment);
+
+ // try to void invoice
+ try {
+ killBillClient.voidInvoice(invoices.get(1).getInvoiceId(), requestOptions);
+ } catch (final KillBillClientException e) {
+ assertTrue(false);
+ }
+
+ // check that account balance is zero
+ account = killBillClient.getAccount(accountJson.getAccountId(), true, true, requestOptions);
+ assertEquals(account.getAccountBalance().compareTo(BigDecimal.ZERO), 0);
+
+ }
+
+ @Test(groups = "slow", description = "Can not void an invoice with partial payment")
+ public void testInvoiceVoidWithPartialPay() throws Exception {
+ final Account accountJson = createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice();
+ assertNotNull(accountJson);
+
+ // Verify we didn't get any invoicePayment
+ final List<InvoicePayment> noPaymentsFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId(), requestOptions);
+ assertEquals(noPaymentsFromJson.size(), 0);
+
+ // Get the invoices
+ List<Invoice> invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+ // 2 invoices but look for the non zero dollar one
+ assertEquals(invoices.size(), 2);
+ // verify account balance
+ Account account = killBillClient.getAccount(accountJson.getAccountId(), true, true, requestOptions);
+ assertEquals(account.getAccountBalance().compareTo(invoices.get(1).getBalance()), 0);
+
+ // process payment
+ InvoicePayment invoicePayment = processPayment(accountJson, invoices.get(1), true);
+
+ // try to void invoice
+ try {
+ killBillClient.voidInvoice(invoices.get(1).getInvoiceId(), requestOptions);
+ Assert.fail("VoidInvoice call should fail with 400");
+ } catch (final KillBillClientException e) {
+ assertTrue(true);
+ }
+
+ }
+
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", description = "Void a child invoice", retryAnalyzer = FlakyRetryAnalyzer.class)
+ public void testChildVoidInvoice() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ final LocalDate triggeredDate = new LocalDate(2012, 5, 26);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account parentAccount = createAccount();
+ final Account childAccount1 = createAccount(parentAccount.getAccountId());
+
+ // Add a bundle and subscription
+ createEntitlement(childAccount1.getAccountId(), UUID.randomUUID().toString(), "Shotgun",
+ ProductCategory.BASE, BillingPeriod.MONTHLY, true);
+
+ // trigger an invoice generation
+ killBillClient.createInvoice(childAccount1.getAccountId(), triggeredDate, requestOptions);
+ List<Invoice> child1Invoices = killBillClient.getInvoicesForAccount(childAccount1.getAccountId(), true, false, requestOptions);
+ assertEquals(child1Invoices.size(), 2);
+
+ // move one day so that the parent invoice is committed
+ clock.addDays(1);
+ crappyWaitForLackOfProperSynchonization();
+ List<Invoice> parentInvoices = killBillClient.getInvoicesForAccount(parentAccount.getAccountId(), true, false, requestOptions);
+ assertEquals(parentInvoices.size(), 1);
+
+ // try to void child invoice
+ killBillClient.voidInvoice(child1Invoices.get(1).getInvoiceId(), requestOptions);
+
+ // move the clock 1 month to check if invoices change
+ clock.addDays(31);
+ crappyWaitForLackOfProperSynchonization();
+
+ // The parent added other invoice, now it has two (duplicate)
+ parentInvoices = killBillClient.getInvoicesForAccount(parentAccount.getAccountId(), true, false, requestOptions);
+ assertEquals(parentInvoices.size(), 2);
+
+ // the child added one invoice as expected
+ child1Invoices = killBillClient.getInvoicesForAccount(childAccount1.getAccountId(), true, false, requestOptions);
+ assertEquals(child1Invoices.size(), 2);
+ }
+
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", description = "Void a parent invoice", retryAnalyzer = FlakyRetryAnalyzer.class)
+ public void testParentVoidInvoice() throws Exception {
+ final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+ final LocalDate triggeredDate = new LocalDate(2012, 5, 26);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account parentAccount = createAccount();
+ final Account childAccount1 = createAccount(parentAccount.getAccountId());
+
+ // Add a bundle and subscription
+ createEntitlement(childAccount1.getAccountId(), UUID.randomUUID().toString(), "Shotgun",
+ ProductCategory.BASE, BillingPeriod.MONTHLY, true);
+
+ // trigger an invoice generation
+ killBillClient.createInvoice(childAccount1.getAccountId(), triggeredDate, requestOptions);
+ List<Invoice> child1Invoices = killBillClient.getInvoicesForAccount(childAccount1.getAccountId(), true, false, requestOptions);
+ assertEquals(child1Invoices.size(), 2);
+
+ // move one day so that the parent invoice is committed
+ clock.addDays(1);
+ crappyWaitForLackOfProperSynchonization();
+ List<Invoice> parentInvoices = killBillClient.getInvoicesForAccount(parentAccount.getAccountId(), true, false, requestOptions);
+ assertEquals(parentInvoices.size(), 1);
+
+ // try to void parent invoice
+ killBillClient.voidInvoice(parentInvoices.get(0).getInvoiceId(), requestOptions);
+
+ // move the clock 1 month to check if invoices change
+ clock.addDays(31);
+ crappyWaitForLackOfProperSynchonization();
+
+ // since the child did not have any change, the parent does not have an invoice
+ // after the void.
+ parentInvoices = killBillClient.getInvoicesForAccount(parentAccount.getAccountId(), true, false, requestOptions);
+ assertEquals(parentInvoices.size(), 0);
+
+ // the child does not have any change
+ child1Invoices = killBillClient.getInvoicesForAccount(childAccount1.getAccountId(), true, false, requestOptions);
+ assertEquals(child1Invoices.size(), 2);
+ }
+
+ private InvoicePayment processPayment(Account accountJson, Invoice invoice, boolean partialPay) throws Exception {
+
+ final BigDecimal payAmount = partialPay ? invoice.getBalance().subtract(BigDecimal.TEN) : invoice.getBalance();
+ final InvoicePayment invoicePayment = new InvoicePayment();
+ invoicePayment.setPurchasedAmount(payAmount);
+ invoicePayment.setAccountId(accountJson.getAccountId());
+ invoicePayment.setTargetInvoiceId(invoice.getInvoiceId());
+
+ final InvoicePayment result = killBillClient.createInvoicePayment(invoicePayment, false, requestOptions);
+ assertEquals(result.getTransactions().size(), 1);
+ assertTrue(result.getTransactions().get(0).getAmount().compareTo(payAmount) == 0);
+
+ return result;
+ }
+
+ private void refundPayment(InvoicePayment payment) throws Exception {
+
+ final InvoicePaymentTransaction refund = new InvoicePaymentTransaction();
+ refund.setPaymentId(payment.getPaymentId());
+ refund.setAmount(payment.getPurchasedAmount());
+ killBillClient.createInvoicePaymentRefund(refund, requestOptions);
+
+ final InvoicePayments allPayments = killBillClient.getInvoicePaymentsForAccount(payment.getAccountId(), requestOptions);
+ assertEquals(allPayments.size(), 1);
+
+ final List<PaymentTransaction> objRefundFromJson = getPaymentTransactions(allPayments, TransactionType.REFUND.toString());
+ assertEquals(objRefundFromJson.size(), 1);
+ }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
index 01cc712..83e3d47 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestJaxrsBase.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -60,6 +60,7 @@ import org.killbill.billing.util.config.definition.PaymentConfig;
import org.killbill.billing.util.config.definition.SecurityConfig;
import org.killbill.bus.api.PersistentBus;
import org.killbill.commons.jdbi.guice.DaoConfig;
+import org.killbill.notificationq.api.NotificationQueueService;
import org.skife.config.ConfigurationObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -105,6 +106,9 @@ public class TestJaxrsBase extends KillbillClient {
protected TestApiListener busHandler;
@Inject
+ protected NotificationQueueService notificationQueueService;
+
+ @Inject
@Named(KillbillServerModule.SHIRO_DATA_SOURCE_ID)
protected DataSource shiroDataSource;
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
index 4b32eba..17cae7a 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -21,22 +21,37 @@ package org.killbill.billing.jaxrs;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.awaitility.Awaitility;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
+import org.joda.time.DateTime;
+import org.killbill.CreatorName;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.client.KillBillClientException;
import org.killbill.billing.client.model.TenantKey;
import org.killbill.billing.jaxrs.json.NotificationJson;
import org.killbill.billing.notification.plugin.api.ExtBusEventType;
+import org.killbill.billing.server.DefaultServerService;
+import org.killbill.billing.server.notifications.PushNotificationKey;
import org.killbill.billing.server.notifications.PushNotificationListener;
import org.killbill.billing.tenant.api.TenantKV;
+import org.killbill.notificationq.NotificationQueueDispatcher;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
+import org.killbill.notificationq.dao.NotificationEventModelDao;
+import org.killbill.queue.QueueObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
@@ -44,6 +59,8 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@@ -125,6 +142,72 @@ public class TestPushNotification extends TestJaxrsBase {
unregisterTenantForCallback(callback);
}
+ @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/726")
+ public void testVerify726Backport() throws Exception {
+ // Record an event without the metadata field
+ final PushNotificationKeyPre726 key = new PushNotificationKeyPre726(UUID.randomUUID(),
+ UUID.randomUUID(),
+ UUID.randomUUID().toString(),
+ UUID.randomUUID().toString(),
+ UUID.randomUUID(),
+ 1,
+ UUID.randomUUID().toString());
+ final String eventJson = QueueObjectMapper.get().writeValueAsString(key);
+ // Need to serialize it manually, to reflect the correct class name
+ final NotificationEventModelDao notificationEventModelDao = new NotificationEventModelDao(CreatorName.get(),
+ clock.getUTCNow(),
+ PushNotificationKey.class.getName(),
+ eventJson,
+ UUID.randomUUID(),
+ internalCallContext.getAccountRecordId(),
+ internalCallContext.getTenantRecordId(),
+ UUID.randomUUID(),
+ clock.getUTCNow(),
+ DefaultServerService.SERVER_SERVICE + ":testVerify726Backport");
+
+ final AtomicReference<PushNotificationKey> notification = new AtomicReference<PushNotificationKey>();
+ // Need to create a custom queue to extract the deserialized event
+ final NotificationQueue notificationQueue = notificationQueueService.createNotificationQueue(DefaultServerService.SERVER_SERVICE,
+ "testVerify726Backport",
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(final NotificationEvent notificationKey, final DateTime eventDateTime, final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
+ if (!(notificationKey instanceof PushNotificationKey)) {
+ Assert.fail();
+ return;
+ }
+ final PushNotificationKey key = (PushNotificationKey) notificationKey;
+ notification.set(key);
+ }
+ }
+ );
+ try {
+ notificationQueue.startQueue();
+ ((NotificationQueueDispatcher) notificationQueueService).getDao().insertEntry(notificationEventModelDao);
+ Awaitility.await()
+ .atMost(10, TimeUnit.SECONDS)
+ .until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return notification.get() != null;
+ }
+ });
+ } finally {
+ notificationQueue.stopQueue();
+ }
+
+ final PushNotificationKey retrievedKey = notification.get();
+ Assert.assertEquals(retrievedKey.getTenantId(), key.tenantId);
+ Assert.assertEquals(retrievedKey.getAccountId(), key.accountId);
+ Assert.assertEquals(retrievedKey.getEventType(), key.eventType);
+ Assert.assertEquals(retrievedKey.getObjectType(), key.objectType);
+ Assert.assertEquals(retrievedKey.getObjectId(), key.objectId);
+ Assert.assertEquals(retrievedKey.getAttemptNumber(), (Integer) key.attemptNumber);
+ Assert.assertEquals(retrievedKey.getUrl(), key.url);
+ // New NULL field
+ Assert.assertNull(retrievedKey.getMetaData());
+ }
+
private void unregisterTenantForCallback(final String callback) throws KillBillClientException {
final TenantKey result = killBillClient.getCallbackNotificationForTenant(requestOptions);
Assert.assertEquals(result.getKey(), TenantKV.TenantKey.PUSH_NOTIFICATION_CB.toString());
@@ -152,7 +235,8 @@ public class TestPushNotification extends TestJaxrsBase {
return callback;
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPushNotificationRetries() throws Exception {
final String callback = registerTenantForCallback();
@@ -205,7 +289,8 @@ public class TestPushNotification extends TestJaxrsBase {
unregisterTenantForCallback(callback);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testPushNotificationRetriesMaxAttemptNumber() throws Exception {
final String callback = registerTenantForCallback();
@@ -231,16 +316,16 @@ public class TestPushNotification extends TestJaxrsBase {
resetCallbackStatusProperties();
- // move clock 15 minutes and get 1st retry
- clock.addDeltaFromReality(900000);
+ // move clock 15 minutes (+10s for flakiness) and get 1st retry
+ clock.addDeltaFromReality(910000);
assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
resetCallbackStatusProperties();
- // move clock an hour and get 2nd retry
- clock.addDeltaFromReality(3600000);
+ // move clock an hour (+10s for flakiness) and get 2nd retry
+ clock.addDeltaFromReality(3610000);
assertAllCallbacksCompleted();
Assert.assertTrue(callbackCompletedWithError);
@@ -374,6 +459,33 @@ public class TestPushNotification extends TestJaxrsBase {
test.setCompleted(withError);
}
}
+ }
+ public static final class PushNotificationKeyPre726 implements NotificationEvent {
+
+ public UUID tenantId;
+ public UUID accountId;
+ public String eventType;
+ public String objectType;
+ public UUID objectId;
+ public int attemptNumber;
+ public String url;
+
+ @JsonCreator
+ public PushNotificationKeyPre726(@JsonProperty("tenantId") final UUID tenantId,
+ @JsonProperty("accountId") final UUID accountId,
+ @JsonProperty("eventType") final String eventType,
+ @JsonProperty("objectType") final String objectType,
+ @JsonProperty("objectId") final UUID objectId,
+ @JsonProperty("attemptNumber") final int attemptNumber,
+ @JsonProperty("url") final String url) {
+ this.tenantId = tenantId;
+ this.accountId = accountId;
+ this.eventType = eventType;
+ this.objectType = objectType;
+ this.objectId = objectId;
+ this.attemptNumber = attemptNumber;
+ this.url = url;
+ }
}
}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
index 5d909f1..3909fa6 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestUsage.java
@@ -125,7 +125,7 @@ public class TestUsage extends TestJaxrsBase {
crappyWaitForLackOfProperSynchonization();
- final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, AuditLevel.MINIMAL, requestOptions);
+ final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, false, false, false, AuditLevel.MINIMAL, requestOptions);
Assert.assertEquals(invoices.size(), 2);
final InvoiceItem usageItem = Iterables.tryFind(invoices.get(1).getItems(), new Predicate<InvoiceItem>() {
profiles/killpay/pom.xml 2(+1 -1)
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
index 25446b9..e167bac 100644
--- a/profiles/killpay/pom.xml
+++ b/profiles/killpay/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>killbill-profiles</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles-killpay</artifactId>
profiles/pom.xml 2(+1 -1)
diff --git a/profiles/pom.xml b/profiles/pom.xml
index 910bf30..493b06b 100644
--- a/profiles/pom.xml
+++ b/profiles/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-profiles</artifactId>
subscription/pom.xml 2(+1 -1)
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 979df0a..259f3c5 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-subscription</artifactId>
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 3af29b0..c56df6e 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -295,7 +295,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
@Override
public SubscriptionBaseBundle inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
- final List<SubscriptionBundleModelDao> existingBundles = entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
+ final List<SubscriptionBundleModelDao> existingBundles = bundle.getExternalKey() == null ? ImmutableList.<SubscriptionBundleModelDao>of()
+ : entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getBundlesForLikeKey(bundle.getExternalKey(), context);
final SubscriptionBaseBundle unusedBundle = findExistingUnusedBundleForExternalKeyAndAccount(existingBundles, entitySqlDaoWrapperFactory);
if (unusedBundle != null) {
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index 369469b..ecfeb0f 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -49,15 +49,10 @@ where id = :id
renameBundleExternalKey(prefix) ::= <<
-update bundles b
-join (select
- record_id
- , external_key
- from
- bundles
- where external_key = :externalKey <AND_CHECK_TENANT("")>) t
-on b.record_id = t.record_id
-set b.external_key = concat('kb', '<prefix>', '-', t.record_id, ':', t.external_key)
+update bundles
+set external_key = concat('kb', '<prefix>', '-', record_id, ':', external_key)
+where external_key = :externalKey
+<AND_CHECK_TENANT("")>
;
>>
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
index 4ed3a8f..8174045 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
@@ -24,6 +24,7 @@ import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -205,7 +206,8 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
// Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
// are as they used to be and we can move forward without hitting cancellation
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testUncancel() throws SubscriptionBillingApiException, SubscriptionBaseApiException {
final String prod = "Shotgun";
final BillingPeriod term = BillingPeriod.MONTHLY;
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index 143041e..ba1229a 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -16,6 +18,7 @@
package org.killbill.billing.subscription.api.user;
+import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
@@ -34,7 +37,6 @@ import org.killbill.billing.subscription.DefaultSubscriptionTestInitializer;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
-import org.mariadb.jdbc.internal.util.dao.QueryException;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -82,7 +84,7 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, false, internalCallContext);
Assert.fail("createBundleForAccount should fail because key already exists");
} catch (final RuntimeException e) {
- assertTrue(e.getCause() instanceof SQLIntegrityConstraintViolationException);
+ assertTrue(e.getCause() instanceof SQLException && (e.getCause() instanceof SQLIntegrityConstraintViolationException || "23505".compareTo(((SQLException) e.getCause()).getSQLState()) == 0));
}
final SubscriptionBaseBundle newBundle = subscriptionInternalApi.createBundleForAccount(bundle.getAccountId(), DefaultSubscriptionTestInitializer.DEFAULT_BUNDLE_KEY, true, internalCallContext);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
index 04f0ab5..79bb057 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiError.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.api.TestApiListener.NextEvent;
import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
@@ -106,7 +107,8 @@ public class TestUserApiError extends SubscriptionTestSuiteNoDB {
}
}
- @Test(groups = "fast")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "fast", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testChangeSubscriptionNonActive() throws SubscriptionBaseApiException {
final SubscriptionBase subscription = testUtil.createSubscription(bundle, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
tenant/pom.xml 2(+1 -1)
diff --git a/tenant/pom.xml b/tenant/pom.xml
index 9c42d0b..16b0d58 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-tenant</artifactId>
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index 866aef7..66b582b 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index f7a100f..8e81ddb 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.19.1-SNAPSHOT</version>
+ <version>0.19.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
index 7f059fc..fcb43c1 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2012 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -128,7 +128,8 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V>
try {
value = baseCacheLoader.compute(key, cacheLoaderArgument);
} catch (final Exception e) {
- logger.warn("Unable to compute cached value for key='{}' and cacheLoaderArgument='{}'", key, cacheLoaderArgument, e);
+ // Remove noisy log (might be expected, see https://github.com/killbill/killbill/issues/842)
+ //logger.warn("Unable to compute cached value for key='{}' and cacheLoaderArgument='{}'", key, cacheLoaderArgument, e);
throw new RuntimeException(e);
}
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
index 65ea53b..2291565 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/InvoiceConfig.java
@@ -28,6 +28,11 @@ import org.skife.config.TimeSpan;
public interface InvoiceConfig extends KillbillConfig {
+ public enum UsageDetailMode {
+ AGGREGATE,
+ DETAIL,
+ }
+
@Config("org.killbill.invoice.maxNumberOfMonthsInFuture")
@Default("36")
@Description("Maximum target date to consider when generating an invoice")
@@ -117,4 +122,14 @@ public interface InvoiceConfig extends KillbillConfig {
@Default("true")
@Description("Whether the invoicing system is enabled")
boolean isInvoicingSystemEnabled(@Param("dummy") final InternalTenantContext tenantContext);
+
+ @Config("org.killbill.invoice.item.result.behavior.mode")
+ @Default("AGGREGATE")
+ @Description("How the result for an item will be reported (aggregate mode or detail mode). ")
+ UsageDetailMode getItemResultBehaviorMode();
+
+ @Config("org.killbill.invoice.item.result.behavior.mode")
+ @Default("AGGREGATE")
+ @Description("How the result for an item will be reported (aggregate mode or detail mode). ")
+ UsageDetailMode getItemResultBehaviorMode(@Param("dummy") final InternalTenantContext tenantContext);
}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java b/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
index 574d3b0..d359a19 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/DefaultPaginationSqlDaoHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2014 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,8 @@
package org.killbill.billing.util.entity.dao;
+import java.io.Closeable;
+import java.io.IOException;
import java.util.Iterator;
import javax.annotation.Nullable;
@@ -26,9 +28,13 @@ import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.util.entity.DefaultPagination;
import org.killbill.billing.util.entity.Entity;
import org.killbill.billing.util.entity.Pagination;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class DefaultPaginationSqlDaoHelper {
+ private static final Logger logger = LoggerFactory.getLogger(DefaultPaginationSqlDaoHelper.class);
+
// Number large enough so that small installations have access to an accurate count
// but small enough to not impact very large deployments
// TODO Should this be configurable per tenant?
@@ -66,14 +72,35 @@ public class DefaultPaginationSqlDaoHelper {
// We usually always want to wrap our queries in an EntitySqlDaoTransactionWrapper... except here.
// Since we want to stream the results out, we don't want to auto-commit when this method returns.
final EntitySqlDao<M, E> sqlDao = transactionalSqlDao.onDemandForStreamingResults(sqlDaoClazz);
- // The count to get maxNbRecords can be expensive on very large datasets. As a heuristic to check how large that number is,
- // we retrieve 1 record at offset SIMPLE_PAGINATION_THRESHOLD (pretty fast). If we've found a record, that means the count is larger
- // than this threshold and we don't issue the full count query
final Long maxNbRecords;
- if (context == null || paginationIteratorBuilder.build((S) sqlDao, SIMPLE_PAGINATION_THRESHOLD, 1L, ordering, context).hasNext()) {
+ if (context == null) {
maxNbRecords = null;
} else {
- maxNbRecords = sqlDao.getCount(context);
+ // The count to get maxNbRecords can be expensive on very large datasets. As a heuristic to check how large that number is,
+ // we retrieve 1 record at offset SIMPLE_PAGINATION_THRESHOLD (pretty fast). If we've found a record, that means the count is larger
+ // than this threshold and we don't issue the full count query
+ final Iterator<M> simplePaginationIterator = paginationIteratorBuilder.build((S) sqlDao, SIMPLE_PAGINATION_THRESHOLD, 1L, ordering, context);
+ final boolean veryLargeDataSet = simplePaginationIterator.hasNext();
+
+ // Make sure to free resources (https://github.com/killbill/killbill/issues/853)
+ if (simplePaginationIterator instanceof Closeable) {
+ // Always the case with the current implementation (simplePaginationIterator is a org.skife.jdbi.v2.ResultIterator)
+ try {
+ ((Closeable) simplePaginationIterator).close();
+ } catch (final IOException e) {
+ logger.warn("Unable to close iterator", e);
+ }
+ } else {
+ while (simplePaginationIterator.hasNext()) {
+ simplePaginationIterator.next();
+ }
+ }
+
+ if (veryLargeDataSet) {
+ maxNbRecords = null;
+ } else {
+ maxNbRecords = sqlDao.getCount(context);
+ }
}
final Iterator<M> results = paginationIteratorBuilder.build((S) sqlDao, offset, limit, ordering, context);
diff --git a/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java b/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java
index bdd5e16..7ea60aa 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/DefaultPagination.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
@@ -16,6 +18,8 @@
package org.killbill.billing.util.entity;
+import java.io.Closeable;
+import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -25,7 +29,7 @@ import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
// Assumes the original offset starts at zero.
-public class DefaultPagination<T> implements Pagination<T> {
+public class DefaultPagination<T> implements Pagination<T>, Closeable {
private final Long currentOffset;
private final Long limit;
@@ -75,6 +79,18 @@ public class DefaultPagination<T> implements Pagination<T> {
}
@Override
+ public void close() throws IOException {
+ if (delegateIterator instanceof Closeable) {
+ // Always the case with the current implementation (delegateIterator is a org.skife.jdbi.v2.ResultIterator)
+ ((Closeable) delegateIterator).close();
+ } else {
+ while (delegateIterator.hasNext()) {
+ delegateIterator.next();
+ }
+ }
+ }
+
+ @Override
public Iterator<T> iterator() {
return delegateIterator;
}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index c103789..154bc1d 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -21,10 +21,13 @@ package org.killbill.billing.util.glue;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.guice.ShiroModule;
import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.util.config.definition.RbacConfig;
+import org.killbill.billing.util.config.definition.SecurityConfig;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
@@ -32,6 +35,7 @@ import org.killbill.billing.util.security.shiro.realm.KillBillOktaRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
+import com.google.inject.Provider;
import com.google.inject.binder.AnnotatedBindingBuilder;
// For Kill Bill library only.
@@ -42,7 +46,6 @@ public class KillBillShiroModule extends ShiroModule {
public static final String KILLBILL_OKTA_PROPERTY = "killbill.server.okta";
public static final String KILLBILL_RBAC_PROPERTY = "killbill.server.rbac";
-
public static boolean isLDAPEnabled() {
return Boolean.parseBoolean(System.getProperty(KILLBILL_LDAP_PROPERTY, "false"));
}
@@ -70,11 +73,25 @@ public class KillBillShiroModule extends ShiroModule {
}).build(RbacConfig.class);
bind(RbacConfig.class).toInstance(config);
- bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
+ final ConfigSource skifeConfigSource = new ConfigSource() {
+ @Override
+ public String getString(final String propertyName) {
+ return configSource.getString(propertyName);
+ }
+ };
+
+ bind(RbacConfig.class).toInstance(config);
+
+ final Provider<IniRealm> iniRealmProvider = RealmsFromShiroIniProvider.getIniRealmProvider(skifeConfigSource);
+ // Hack for Kill Bill library to work around weird Guice ClassCastException when using
+ // bindRealm().toInstance(...) -- this means we don't support custom realms when embedding Kill Bill
+ bindRealm().toProvider(iniRealmProvider).asEagerSingleton();
configureJDBCRealm();
configureLDAPRealm();
+
+ configureOktaRealm();
}
protected void configureJDBCRealm() {
diff --git a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java
index 77db66f..3d0877e 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagDefinition.java
@@ -156,7 +156,11 @@ public class DefaultTagDefinition extends EntityBase implements TagDefinition {
throw new IllegalStateException(String.format("ControlTag id %s does not seem to exist", id));
}
- private static List<ObjectType> toObjectTypes(final String input) {
+ private static List<ObjectType> toObjectTypes(@Nullable final String input) {
+ if (input == null) {
+ return ImmutableList.copyOf(ObjectType.values());
+ }
+
return ImmutableList.copyOf(Iterables.transform(SPLITTER.splitToList(input), new Function<String, ObjectType>() {
@Override
public ObjectType apply(final String input) {
diff --git a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java
index e4af036..81f5282 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/DefaultTagInternalApi.java
@@ -21,6 +21,7 @@ import java.util.UUID;
import javax.inject.Inject;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -74,8 +75,14 @@ public class DefaultTagInternalApi implements TagInternalApi {
public void addTag(final UUID objectId, final ObjectType objectType, final UUID tagDefinitionId, final InternalCallContext context)
throws TagApiException {
final TagModelDao tag = new TagModelDao(context.getCreatedDate(), tagDefinitionId, objectId, objectType);
- tagDao.create(tag, context);
-
+ try {
+ tagDao.create(tag, context);
+ } catch (TagApiException e) {
+ // Be lenient here and make the addTag method idempotent
+ if (ErrorCode.TAG_ALREADY_EXISTS.getCode() != e.getCode()) {
+ throw e;
+ }
+ }
}
@Override
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl.sql b/util/src/main/resources/org/killbill/billing/util/ddl.sql
index 5523655..b1430a6 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl.sql
@@ -156,7 +156,7 @@ DROP TABLE IF EXISTS notifications;
CREATE TABLE notifications (
record_id serial unique,
class_name varchar(256) NOT NULL,
- event_json varchar(2048) NOT NULL,
+ event_json text NOT NULL,
user_token varchar(36),
created_date datetime NOT NULL,
creating_owner varchar(50) NOT NULL,
@@ -180,7 +180,7 @@ DROP TABLE IF EXISTS notifications_history;
CREATE TABLE notifications_history (
record_id serial unique,
class_name varchar(256) NOT NULL,
- event_json varchar(2048) NOT NULL,
+ event_json text NOT NULL,
user_token varchar(36),
created_date datetime NOT NULL,
creating_owner varchar(50) NOT NULL,
@@ -201,7 +201,7 @@ DROP TABLE IF EXISTS bus_events;
CREATE TABLE bus_events (
record_id serial unique,
class_name varchar(128) NOT NULL,
- event_json varchar(2048) NOT NULL,
+ event_json text NOT NULL,
user_token varchar(36),
created_date datetime NOT NULL,
creating_owner varchar(50) NOT NULL,
@@ -221,7 +221,7 @@ DROP TABLE IF EXISTS bus_events_history;
CREATE TABLE bus_events_history (
record_id serial unique,
class_name varchar(128) NOT NULL,
- event_json varchar(2048) NOT NULL,
+ event_json text NOT NULL,
user_token varchar(36),
created_date datetime NOT NULL,
creating_owner varchar(50) NOT NULL,
diff --git a/util/src/main/resources/org/killbill/billing/util/migration/V20180202093043__increase_field_event_json.sql b/util/src/main/resources/org/killbill/billing/util/migration/V20180202093043__increase_field_event_json.sql
new file mode 100644
index 0000000..a0c4568
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/migration/V20180202093043__increase_field_event_json.sql
@@ -0,0 +1,4 @@
+alter table bus_events modify event_json text not null;
+alter table bus_events_history modify event_json text not null;
+alter table notifications modify event_json text not null;
+alter table notifications_history modify event_json text not null;
util/src/main/resources/trimTenant.sql 75(+75 -0)
diff --git a/util/src/main/resources/trimTenant.sql b/util/src/main/resources/trimTenant.sql
new file mode 100644
index 0000000..d0bfb0c
--- /dev/null
+++ b/util/src/main/resources/trimTenant.sql
@@ -0,0 +1,75 @@
+drop procedure if exists trimTenant;
+DELIMITER //
+CREATE PROCEDURE trimTenant(p_api_key varchar(36))
+BEGIN
+
+ DECLARE v_tenant_record_id bigint /*! unsigned */;
+
+ select record_id from tenants WHERE api_key = p_api_key into v_tenant_record_id;
+
+ DELETE FROM analytics_account_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_account_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_account_transitions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_accounts WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_bundle_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_bundle_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_bundles WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_adjustments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_credits WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_item_adjustments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_items WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_payment_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoice_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_invoices WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_notifications WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM analytics_notifications_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM analytics_payment_auths WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_captures WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_chargebacks WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_credits WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_method_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_purchases WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_refunds WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_tags WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_payment_voids WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_subscription_transitions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM analytics_transaction_fields WHERE tenant_record_id = v_tenant_record_id;
+
+ DELETE FROM account_email_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM account_emails WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM account_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM accounts WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM audit_log WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM blocking_states WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM bundles WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM bus_events WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM bus_events_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM bus_ext_events WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM bus_ext_events_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM custom_field_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM custom_fields WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoice_items WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoice_parent_children WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoice_payments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM invoices WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM notifications WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM notifications_history WHERE search_key2 = v_tenant_record_id;
+ DELETE FROM payment_attempt_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_attempts WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_method_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_methods WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_transaction_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payment_transactions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM payments WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM rolled_up_usage WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM subscription_events WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM subscriptions WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM tag_history WHERE tenant_record_id = v_tenant_record_id;
+ DELETE FROM tags WHERE tenant_record_id = v_tenant_record_id;
+
+ END;
+//
+DELIMITER ;
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index 2808866..d940406 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -19,18 +19,27 @@
package org.killbill.billing;
import java.io.IOException;
+import java.io.PrintWriter;
import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLNonTransientConnectionException;
import java.util.Enumeration;
-import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.sql.DataSource;
import org.killbill.billing.platform.test.PlatformDBTestingHelper;
import org.killbill.billing.util.glue.IDBISetup;
import org.killbill.billing.util.io.IOUtils;
import org.killbill.commons.embeddeddb.EmbeddedDB;
+import org.killbill.commons.jdbi.guice.DBIProvider;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.ResultSetMapperFactory;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.base.MoreObjects;
@@ -38,7 +47,7 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
private static DBTestingHelper dbTestingHelper = null;
- private AtomicBoolean initialized;
+ private DBI dbi;
public static synchronized DBTestingHelper get() {
if (dbTestingHelper == null) {
@@ -49,18 +58,18 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
private DBTestingHelper() {
super();
- initialized = new AtomicBoolean(false);
}
@Override
- public IDBI getDBI() {
- final DBI dbi = (DBI) super.getDBI();
- // Register KB specific mappers
- if (initialized.compareAndSet(false, true)) {
+ public synchronized IDBI getDBI() {
+ if (dbi == null) {
+ final RetryableDataSource retryableDataSource = new RetryableDataSource(getDataSource());
+ dbi = (DBI) new DBIProvider(null, retryableDataSource, null).get();
+
+ // Register KB specific mappers
for (final ResultSetMapperFactory resultSetMapperFactory : IDBISetup.mapperFactoriesToRegister()) {
dbi.registerMapper(resultSetMapperFactory);
}
-
for (final ResultSetMapper resultSetMapper : IDBISetup.mappersToRegister()) {
dbi.registerMapper(resultSetMapper);
}
@@ -202,4 +211,73 @@ public class DBTestingHelper extends PlatformDBTestingHelper {
}
}
}
+
+ // DataSource which will retry recreating a connection once in case of a connection exception.
+ // This is useful for transient network errors in tests when using a separate database (e.g. Docker container),
+ // as we don't use a connection pool.
+ private static final class RetryableDataSource implements DataSource {
+
+ private static final Logger logger = LoggerFactory.getLogger(RetryableDataSource.class);
+
+ private final DataSource delegate;
+
+ private RetryableDataSource(final DataSource delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ try {
+ return delegate.getConnection();
+ } catch (final SQLNonTransientConnectionException e) {
+ logger.warn("Unable to retrieve connection, attempting to retry", e);
+ return delegate.getConnection();
+ }
+ }
+
+ @Override
+ public Connection getConnection(final String username, final String password) throws SQLException {
+ try {
+ return delegate.getConnection(username, password);
+ } catch (final SQLNonTransientConnectionException e) {
+ logger.warn("Unable to retrieve connection, attempting to retry", e);
+ return delegate.getConnection(username, password);
+ }
+ }
+
+ @Override
+ public <T> T unwrap(final Class<T> iface) throws SQLException {
+ return delegate.unwrap(iface);
+ }
+
+ @Override
+ public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+ return delegate.isWrapperFor(iface);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return delegate.getLogWriter();
+ }
+
+ @Override
+ public void setLogWriter(final PrintWriter out) throws SQLException {
+ delegate.setLogWriter(out);
+ }
+
+ @Override
+ public void setLoginTimeout(final int seconds) throws SQLException {
+ delegate.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return delegate.getLoginTimeout();
+ }
+
+ //@Override
+ public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ throw new SQLFeatureNotSupportedException("javax.sql.DataSource.getParentLogger() is not currently supported by " + this.getClass().getName());
+ }
+ }
}
diff --git a/util/src/test/java/org/killbill/billing/util/listener/TestRetryableService.java b/util/src/test/java/org/killbill/billing/util/listener/TestRetryableService.java
index c33b881..e7e492d 100644
--- a/util/src/test/java/org/killbill/billing/util/listener/TestRetryableService.java
+++ b/util/src/test/java/org/killbill/billing/util/listener/TestRetryableService.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -26,6 +26,7 @@ import org.joda.time.DateTime;
import org.joda.time.Period;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.ImmutableAccountData;
+import org.killbill.billing.api.FlakyRetryAnalyzer;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.events.BusInternalEvent;
import org.killbill.billing.events.ControlTagCreationInternalEvent;
@@ -84,7 +85,8 @@ public class TestRetryableService extends UtilTestSuiteWithEmbeddedDB {
testListener.stop();
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testFixUp() throws Exception {
testListener.throwRetryableException = true;
@@ -114,7 +116,8 @@ public class TestRetryableService extends UtilTestSuiteWithEmbeddedDB {
Assert.assertEquals(getFutureRetryableEvents().size(), 0);
}
- @Test(groups = "slow")
+ // Flaky, see https://github.com/killbill/killbill/issues/860
+ @Test(groups = "slow", retryAnalyzer = FlakyRetryAnalyzer.class)
public void testGiveUp() throws Exception {
testListener.throwRetryableException = true;