killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java 3(+2 -1)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 178(+131 -47)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java 93(+58 -35)
invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java 17(+10 -7)
invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java 11(+6 -5)
invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java 39(+23 -16)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java 4(+3 -1)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java 31(+16 -15)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalUsageInArrear.java 93(+79 -14)
invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg 48(+48 -0)
invoice/src/main/resources/org/killbill/billing/invoice/migration/V20181129164135__tracking_ids.sql 16(+16 -0)
invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java 30(+15 -15)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalCapacityInArrear.java 47(+27 -20)
invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java 94(+50 -44)
Details
diff --git a/api/src/main/java/org/killbill/billing/usage/RawUsage.java b/api/src/main/java/org/killbill/billing/usage/RawUsage.java
index b57a3dc..fd43f04 100644
--- a/api/src/main/java/org/killbill/billing/usage/RawUsage.java
+++ b/api/src/main/java/org/killbill/billing/usage/RawUsage.java
@@ -30,4 +30,6 @@ public interface RawUsage {
String getUnitType();
Long getAmount();
+
+ String getTrackingId();
}
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 a405fbe..a7140c3 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
@@ -320,7 +320,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return;
}
-
final InvoiceConfig defaultInvoiceConfig = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
invoiceConfig = new ConfigurableInvoiceConfig(defaultInvoiceConfig);
final Injector g = Guice.createInjector(Stage.PRODUCTION, new BeatrixIntegrationModule(configSource, clock, invoiceConfig));
@@ -596,7 +595,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
}, events);
}
-
protected Payment refundPaymentWithInvoiceItemAdjAndCheckForCompletion(final Account account, final Payment payment, final Map<UUID, BigDecimal> iias, final NextEvent... events) {
return refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, payment.getPurchasedAmount(), payment.getCurrency(), iias, events);
}
@@ -888,7 +886,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
remove_account_Tag(id, ControlTagType.AUTO_PAY_OFF, type, additionalEvents);
}
-
protected void remove_AUTO_INVOICING_OFF_Tag(final UUID id, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
remove_account_Tag(id, ControlTagType.AUTO_INVOICING_OFF, type, additionalEvents);
}
@@ -897,8 +894,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
remove_account_Tag(id, ControlTagType.AUTO_INVOICING_DRAFT, type, additionalEvents);
}
-
- private void remove_account_Tag(final UUID id, final ControlTagType controlTagType, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
+ private void remove_account_Tag(final UUID id, final ControlTagType controlTagType, final ObjectType type, final NextEvent... additionalEvents) throws TagDefinitionApiException, TagApiException {
busHandler.pushExpectedEvent(NextEvent.TAG);
busHandler.pushExpectedEvents(additionalEvents);
tagUserApi.removeTag(id, type, controlTagType.getId(), callContext);
@@ -918,16 +914,20 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return result;
}
- protected void recordUsageData(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) throws UsageApiException {
+ protected void recordUsageData(final UUID subscriptionId, final String trackingId, 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));
final List<UnitUsageRecord> unitUsageRecords = new ArrayList<UnitUsageRecord>();
unitUsageRecords.add(new UnitUsageRecord(unitType, usageRecords));
- final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, UUID.randomUUID().toString(), unitUsageRecords);
+ final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, trackingId, unitUsageRecords);
usageUserApi.recordRolledUpUsage(record, context);
}
+ protected void recordUsageData(final SubscriptionUsageRecord usageRecord, final CallContext context) throws UsageApiException {
+ usageUserApi.recordRolledUpUsage(usageRecord, context);
+ }
+
protected void removeUsageData(final UUID subscriptionId, final String unitType, final LocalDate recordedDate) {
dbi.withHandle(new HandleCallback<Void>() {
@Override
@@ -939,7 +939,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
});
}
-
protected static class TestDryRunArguments implements DryRunArguments {
private final DryRunType dryRunType;
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 88bd103..65e9c2e 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
@@ -53,6 +53,7 @@ import org.killbill.billing.payment.api.TransactionStatus;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -720,7 +721,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
newItems.add(recurring3);
newItems.add(repair3);
shellInvoice.addInvoiceItems(newItems);
- invoiceDao.createInvoice(shellInvoice, new FutureAccountNotifications(), internalCallContext);
+ invoiceDao.createInvoice(shellInvoice, ImmutableSet.of(), new FutureAccountNotifications(), internalCallContext);
// Move ahead one month, verify nothing from previous data was generated
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 652f4ea..d418097 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
@@ -813,8 +813,8 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
assertNull(bpSubscription.getSubscriptionBase().getChargedThroughDate());
// Record usage for first month
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 5), 100L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 4, 5), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
// 2012-05-01
busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -843,8 +843,8 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 5), InvoiceItemType.USAGE, BigDecimal.ZERO));
// Record usage for second month
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 5), 100L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 4), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 5, 5), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 6, 4), 100L, callContext);
// 2012-06-01
busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
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 551822f..b9938a5 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
@@ -18,6 +18,8 @@
package org.killbill.billing.beatrix.integration.usage;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -30,12 +32,19 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageRecord;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import static org.testng.Assert.assertEquals;
public class TestConsumableInArrear extends TestIntegrationBase {
@@ -62,44 +71,47 @@ public class TestConsumableInArrear extends TestIntegrationBase {
//
final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-1", "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-2", "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
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(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("tracking-1", "tracking-2"), internalCallContext);
// $0 invoice
busHandler.pushExpectedEvents(NextEvent.INVOICE);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 3, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of(), internalCallContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 1), 50L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 6, 16), 300L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-3", "bullets", new LocalDate(2012, 6, 1), 50L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-4", "bullets", new LocalDate(2012, 6, 16), 300L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 4, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("tracking-3", "tracking-4"), internalCallContext);
// Should be ignored because this is outside of optimization range (org.killbill.invoice.readMaxRawUsagePreviousPeriod = 2) => we will only look for items > 2012-7-1 - 2 months = 2012-5-1
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 30), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-5", "bullets", new LocalDate(2012, 4, 30), 100L, callContext);
// Should be invoiced from past period
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 1), 199L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-6", "bullets", new LocalDate(2012, 5, 1), 199L, callContext);
// New usage for this past period
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 1), 50L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 7, 16), 300L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-7", "bullets", new LocalDate(2012, 7, 1), 50L, callContext);
+ recordUsageData(aoSubscription.getId(), "tracking-8", "bullets", new LocalDate(2012, 7, 16), 300L, callContext);
// Remove old data, should be ignored by the system because readMaxRawUsagePreviousPeriod = 2, so:
// * Last endDate invoiced is 2012-7-1 => Anything 2 period prior that will be ignored => Anything prior 2012-5-1 should be ignored
@@ -109,25 +121,26 @@ public class TestConsumableInArrear extends TestIntegrationBase {
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 5, 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")));
-
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 5, 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")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("tracking-6", "tracking-7", "tracking-8"), internalCallContext);
// 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 = 6;
for (int i = 0; i < 8; i++) {
- recordUsageData(aoSubscription.getId(), "bullets", startDate.plusDays(15), 350L, callContext);
+ final String trackingId = "tracking-" + (9 + i);
+ recordUsageData(aoSubscription.getId(), trackingId, "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")));
-
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), currentInvoice, callContext,
+ new ExpectedInvoiceItemCheck(startDate, startDate.plusMonths(1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of(trackingId), internalCallContext);
startDate = startDate.plusMonths(1);
currentInvoice++;
@@ -157,29 +170,31 @@ public class TestConsumableInArrear extends TestIntegrationBase {
//
final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "t1", "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
+ recordUsageData(aoSubscription.getId(), "t2", "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
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(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 3), 99L, callContext);
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 5), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "t3", "bullets", new LocalDate(2012, 5, 3), 99L, callContext);
+ recordUsageData(aoSubscription.getId(), "t4", "bullets", new LocalDate(2012, 5, 5), 100L, callContext);
// This one should be ignored
- recordUsageData(aoSubscription.getId(), "bullets", new LocalDate(2012, 5, 29), 100L, callContext);
+ recordUsageData(aoSubscription.getId(), "t5", "bullets", new LocalDate(2012, 5, 29), 100L, callContext);
clock.addDays(27);
busHandler.pushExpectedEvents(NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
aoSubscription.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(2012, 5, 28), BillingActionPolicy.IMMEDIATE, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 3, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 28), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 28), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t3", "t4"), internalCallContext);
busHandler.pushExpectedEvent(NextEvent.NULL_INVOICE);
clock.addDays(4);
@@ -204,44 +219,113 @@ public class TestConsumableInArrear extends TestIntegrationBase {
Assert.assertNull(bpSubscription.getSubscriptionBase().getChargedThroughDate());
// Record usage for first month
- recordUsageData(bpSubscription.getId(), "stones", new LocalDate(2012, 4, 5), 85L, callContext);
- recordUsageData(bpSubscription.getId(), "stones", new LocalDate(2012, 4, 15), 150L, callContext);
+ recordUsageData(bpSubscription.getId(), "xxx-1", "stones", new LocalDate(2012, 4, 5), 85L, callContext);
+ recordUsageData(bpSubscription.getId(), "xxx-2", "stones", new LocalDate(2012, 4, 15), 150L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 1, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("1000")));
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("1000")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("xxx-1", "xxx-2"), internalCallContext);
final DateTime firstExpectedCTD = account.getReferenceTime().withMonthOfYear(5).withDayOfMonth(1);
- Assert.assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(firstExpectedCTD), 0);
+ assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(firstExpectedCTD), 0);
// $0 invoice
busHandler.pushExpectedEvents(NextEvent.INVOICE);
clock.addMonths(1);
assertListenerStatus();
-
- invoiceChecker.checkInvoice(account.getId(), 2, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of(), internalCallContext);
final DateTime secondExpectedCTD = account.getReferenceTime().withMonthOfYear(6).withDayOfMonth(1);
- Assert.assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(secondExpectedCTD), 0);
+ assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(secondExpectedCTD), 0);
// Record usage for third month (verify invoicing resumes)
- recordUsageData(bpSubscription.getId(), "stones", new LocalDate(2012, 6, 5), 25L, callContext);
- recordUsageData(bpSubscription.getId(), "stones", new LocalDate(2012, 6, 15), 50L, callContext);
+ recordUsageData(bpSubscription.getId(), "xxx-3", "stones", new LocalDate(2012, 6, 5), 25L, callContext);
+ recordUsageData(bpSubscription.getId(), "xxx-4", "stones", new LocalDate(2012, 6, 15), 50L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 3, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("100")));
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("100")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("xxx-3", "xxx-4"), internalCallContext);
final DateTime thirdExpectedCTD = account.getReferenceTime().withMonthOfYear(7).withDayOfMonth(1);
- Assert.assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(thirdExpectedCTD), 0);
+ assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(thirdExpectedCTD), 0);
+ }
+
+ @Test(groups = "slow")
+ public void testWithMultipleUsageSubscriptions() throws Exception {
+ // We take april as it has 30 days (easier to play with BCD)
+ // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+ clock.setDay(new LocalDate(2012, 4, 1));
+
+ final AccountData accountData = getAccountData(1);
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+ // Create subscription
+ final DefaultEntitlement bp1 = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey1", "Trebuchet", ProductCategory.BASE, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ subscriptionChecker.checkSubscriptionCreated(bp1.getId(), internalCallContext);
+
+ final DefaultEntitlement bp2 = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey2", "Trebuchet", ProductCategory.BASE, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ subscriptionChecker.checkSubscriptionCreated(bp2.getId(), internalCallContext);
+
+ final List<UsageRecord> bp1StoneRecords1 = new ArrayList();
+ bp1StoneRecords1.add(new UsageRecord(new LocalDate(2012, 4, 5), 5L));
+ bp1StoneRecords1.add(new UsageRecord(new LocalDate(2012, 4, 15), 10L));
+ bp1StoneRecords1.add(new UsageRecord(new LocalDate(2012, 4, 16), 15L));
+ final SubscriptionUsageRecord bp1UsageRecord1 = new SubscriptionUsageRecord(bp1.getId(), "bp1-tracking-1", ImmutableList.of(new UnitUsageRecord("stones", bp1StoneRecords1)));
+ recordUsageData(bp1UsageRecord1, callContext);
+
+ final List<UsageRecord> bp1StoneRecords2 = new ArrayList();
+ bp1StoneRecords2.add(new UsageRecord(new LocalDate(2012, 4, 23), 10L));
+ // Outside of range for this period -> Its tracking ID spreads across 2 invoices
+ bp1StoneRecords2.add(new UsageRecord(new LocalDate(2012, 5, 1), 101L));
+ final SubscriptionUsageRecord bp1UsageRecord2 = new SubscriptionUsageRecord(bp1.getId(), "bp1-tracking-2", ImmutableList.of(new UnitUsageRecord("stones", bp1StoneRecords2)));
+ recordUsageData(bp1UsageRecord2, callContext);
+
+
+
+ final List<UsageRecord> bp2StoneRecords = new ArrayList();
+ bp2StoneRecords.add(new UsageRecord(new LocalDate(2012, 4, 5), 85L));
+ bp2StoneRecords.add(new UsageRecord(new LocalDate(2012, 4, 15), 150L));
+ bp2StoneRecords.add(new UsageRecord(new LocalDate(2012, 4, 16), 39L));
+ final SubscriptionUsageRecord bp2UsageRecord = new SubscriptionUsageRecord(bp2.getId(), "bp2-tracking-1", ImmutableList.of(new UnitUsageRecord("stones", bp2StoneRecords)));
+ recordUsageData(bp2UsageRecord, callContext);
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("100")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("1000")));
+
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("bp1-tracking-1", "bp1-tracking-2", "bp2-tracking-1"), internalCallContext);
+
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, new BigDecimal("0")),
+ new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, new BigDecimal("1000")));
+
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("bp1-tracking-2"), internalCallContext);
+
+
}
+
+
+
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java
index fd6c4dc..6f2971d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java
@@ -19,6 +19,7 @@ package org.killbill.billing.beatrix.integration.usage;
import java.math.BigDecimal;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -31,12 +32,16 @@ import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UnitUsageRecord;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
@@ -63,17 +68,19 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 1), 143L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 18), 57L, callContext);
+ recordUsageData(entitlementId, "t1", "kilowatt-hour", new LocalDate(2016, 4, 1), 143L, callContext);
+ recordUsageData(entitlementId, "t2", "kilowatt-hour", new LocalDate(2016, 4, 18), 57L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 1, callContext,
- new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("300.00")));
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("300.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
+
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 2), 100L, callContext); // -> Uses v1 : $150
+ recordUsageData(entitlementId, "t3", "kilowatt-hour", new LocalDate(2016, 5, 2), 100L, callContext); // -> Uses v1 : $150
// Catalog change with new price on 2016-05-08
// Schedule CHANGE_PLAN on 2016-05-09
@@ -86,17 +93,20 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
clock.addDays(8);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 9), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t3"), internalCallContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 10), 100L, callContext); // -> Uses v2 : $250
+ recordUsageData(entitlementId, "t4", "kilowatt-hour", new LocalDate(2016, 5, 10), 100L, callContext); // -> Uses v2 : $250
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addDays(23);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 9), new LocalDate(2016, 6, 1), InvoiceItemType.USAGE, new BigDecimal("250.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t4"), internalCallContext);
+
}
@@ -114,17 +124,18 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec1), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 1), 143L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 18), 57L, callContext);
+ recordUsageData(entitlementId, "t1", "kilowatt-hour", new LocalDate(2016, 4, 1), 143L, callContext);
+ recordUsageData(entitlementId, "t2", "kilowatt-hour", new LocalDate(2016, 4, 18), 57L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 1, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("300.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 2), 100L, callContext); // -> Uses v1 : $150
+ recordUsageData(entitlementId, "t3", "kilowatt-hour", new LocalDate(2016, 5, 2), 100L, callContext); // -> Uses v1 : $150
final Entitlement bp = entitlementApi.getEntitlementForId(entitlementId, callContext);
@@ -137,17 +148,20 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
clock.addDays(8);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 9), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t3"), internalCallContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 10), 100L, callContext); // -> Uses special plan : $100
+
+ recordUsageData(entitlementId, "t4", "kilowatt-hour", new LocalDate(2016, 5, 10), 100L, callContext); // -> Uses special plan : $100
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addDays(23);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 9), new LocalDate(2016, 6, 1), InvoiceItemType.USAGE, new BigDecimal("100.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t4"), internalCallContext);
}
@@ -166,43 +180,47 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 5), 1L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 5), 99L, callContext);
+ recordUsageData(entitlementId, "t1", "kilowatt-hour", new LocalDate(2016, 4, 5), 1L, callContext);
+ recordUsageData(entitlementId, "t2", "kilowatt-hour", new LocalDate(2016, 4, 5), 99L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 1, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
+
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 5), 100L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 8), 900L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 9), 200L, callContext); // Move to tier 2.
+ recordUsageData(entitlementId, "t3", "kilowatt-hour", new LocalDate(2016, 5, 5), 100L, callContext);
+ recordUsageData(entitlementId, "t4", "kilowatt-hour", new LocalDate(2016, 5, 8), 900L, callContext);
+ recordUsageData(entitlementId, "t5", "kilowatt-hour", new LocalDate(2016, 5, 9), 200L, callContext); // Move to tier 2.
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 6, 1), InvoiceItemType.USAGE, new BigDecimal("1900.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t3", "t4", "t5"), internalCallContext);
// Remove Usage data from period 2016-5-1 -> 2016-6-1 and verify there is no issue (readMaxRawUsagePreviousPeriod = 0 => We ignore any past invoiced period)
// Full deletion on the second tier
removeUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 9));
//
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 6, 5), 100L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 6, 8), 900L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 6, 12), 50L, callContext); // Move to tier 2.
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 6, 13), 50L, callContext);
+ recordUsageData(entitlementId, "t6", "kilowatt-hour", new LocalDate(2016, 6, 5), 100L, callContext);
+ recordUsageData(entitlementId, "t7", "kilowatt-hour", new LocalDate(2016, 6, 8), 900L, callContext);
+ recordUsageData(entitlementId, "t8", "kilowatt-hour", new LocalDate(2016, 6, 12), 50L, callContext); // Move to tier 2.
+ recordUsageData(entitlementId, "t9", "kilowatt-hour", new LocalDate(2016, 6, 13), 50L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 3, callContext, ImmutableList.<ExpectedInvoiceItemCheck>of(
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext, ImmutableList.<ExpectedInvoiceItemCheck>of(
new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 1), new LocalDate(2016, 7, 1), InvoiceItemType.USAGE, new BigDecimal("1700.00"))));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t6", "t7", "t8", "t9"), internalCallContext);
// Remove Usage data from period 2016-6-1 -> 2016-7-1 and verify there is no issue (readMaxRawUsagePreviousPeriod = 0 => We ignore any past invoiced period)
// Partial deletion on the second tier
@@ -214,8 +232,10 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
assertListenerStatus();
// Check invoicing occurred and - i.e system did not detect deletion of passed invoiced data.
- invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 4, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 1), new LocalDate(2016, 8, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of(), internalCallContext);
+
}
@Test(groups = "slow")
@@ -232,31 +252,33 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
assertListenerStatus();
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 5), 1L, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 5), 99L, callContext);
+ recordUsageData(entitlementId, "t1", "kilowatt-hour", new LocalDate(2016, 4, 5), 1L, callContext);
+ recordUsageData(entitlementId, "t2", "kilowatt-hour", new LocalDate(2016, 4, 5), 99L, callContext);
busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addMonths(1);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ Invoice curInvoice = invoiceChecker.checkInvoice(account.getId(), 1, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
final Entitlement bp = entitlementApi.getEntitlementForId(entitlementId, callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 2), 100L, callContext);
+ recordUsageData(entitlementId, "t3", "kilowatt-hour", new LocalDate(2016, 5, 2), 100L, callContext);
// Update subscription BCD
bp.updateBCD(9, new LocalDate(2016, 5, 9), callContext);
- recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 5, 10), 10L, callContext);
+ recordUsageData(entitlementId, "t4", "kilowatt-hour", new LocalDate(2016, 5, 10), 10L, callContext);
busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
clock.addDays(8);
assertListenerStatus();
- invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 9), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t3"), internalCallContext);
// Original notification before we change BCD
busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
@@ -268,8 +290,9 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
assertListenerStatus();
// NOTE: Is using the new version of the catalog Utility-v2 (effectiveDateForExistingSubscriptions = 2016-05-08T00:00:00+00:00) what we want or is this a bug?
- invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ curInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 9), new LocalDate(2016, 6, 9), InvoiceItemType.USAGE, new BigDecimal("25.00")));
+ invoiceChecker.checkTrackingIds(curInvoice, ImmutableSet.of("t4"), internalCallContext);
}
}
\ No newline at end of file
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 36b0ee7..bdfd46a 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
@@ -20,9 +20,11 @@ package org.killbill.billing.beatrix.util;
import java.math.BigDecimal;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
import org.joda.time.LocalDate;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.EntitlementApi;
import org.killbill.billing.entitlement.api.EntitlementApiException;
@@ -31,17 +33,23 @@ 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.InvoiceUserApi;
+import org.killbill.billing.invoice.dao.InvoiceTrackingModelDao;
+import org.killbill.billing.invoice.dao.InvoiceTrackingSqlDao;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.util.callcontext.CallContext;
+import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
+import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
+import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -53,12 +61,14 @@ public class InvoiceChecker {
private final InvoiceUserApi invoiceUserApi;
private final EntitlementApi entitlementApi;
private final AuditChecker auditChecker;
+ private final IDBI dbi;
@Inject
- public InvoiceChecker(final InvoiceUserApi invoiceUserApi, final EntitlementApi entitlementApi, final AuditChecker auditChecker) {
+ public InvoiceChecker(final InvoiceUserApi invoiceUserApi, final EntitlementApi entitlementApi, final AuditChecker auditChecker, final IDBI dbi) {
this.invoiceUserApi = invoiceUserApi;
this.entitlementApi = entitlementApi;
this.auditChecker = auditChecker;
+ this.dbi = dbi;
}
public Invoice checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final CallContext context, final ExpectedInvoiceItemCheck... expected) throws InvoiceApiException {
@@ -170,6 +180,26 @@ public class InvoiceChecker {
}
}
+
+ public void checkTrackingIds(final Invoice invoice, final Set<String> expectedTrackingIds, final InternalCallContext internalCallContext) {
+ final InvoiceTrackingSqlDao dao = dbi.onDemand(InvoiceTrackingSqlDao.class);
+ final List<InvoiceTrackingModelDao> result = dao.getTrackingsForInvoice(invoice.getId().toString(), internalCallContext);
+
+ final Set<String> existingTrackingIds = ImmutableSet.copyOf(Iterables.transform(result, new Function<InvoiceTrackingModelDao, String>() {
+ @Override
+ public String apply(final InvoiceTrackingModelDao input) {
+ return input.getTrackingId();
+ }
+ }));
+
+ assertEquals(existingTrackingIds.size(), expectedTrackingIds.size());
+ for (final String cur : existingTrackingIds) {
+ assertTrue(expectedTrackingIds.contains(cur));
+ }
+ }
+
+
+
public static class ExpectedInvoiceItemCheck {
private final boolean checkDates;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
index ccc5669..f47254c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -58,6 +58,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class InvoiceApiHelper {
@@ -115,7 +116,7 @@ public class InvoiceApiHelper {
invoiceModelDaos.add(invoiceModelDao);
}
- final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
+ final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, ImmutableSet.of(), internalCallContext);
success = true;
return fromInvoiceItemModelDao(createdInvoiceItems);
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 90f3637..30aa18d 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
@@ -85,6 +85,7 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -468,7 +469,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}));
migrationInvoice.addInvoiceItems(itemModelDaos);
- dao.createInvoices(ImmutableList.<InvoiceModelDao>of(migrationInvoice), internalCallContext);
+ dao.createInvoices(ImmutableList.<InvoiceModelDao>of(migrationInvoice), ImmutableSet.of(), internalCallContext);
return migrationInvoice.getId();
}
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 cc48631..570d678 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
@@ -305,18 +305,21 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
@Override
public void createInvoice(final InvoiceModelDao invoice,
+ final Set<InvoiceTrackingModelDao> trackingIds,
final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context) {
- createInvoices(ImmutableList.<InvoiceModelDao>of(invoice), callbackDateTimePerSubscriptions, context);
+ createInvoices(ImmutableList.<InvoiceModelDao>of(invoice), trackingIds, callbackDateTimePerSubscriptions, context);
}
@Override
public List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoices,
+ final Set<InvoiceTrackingModelDao> trackingIds,
final InternalCallContext context) {
- return createInvoices(invoices, new FutureAccountNotifications(), context);
+ return createInvoices(invoices, trackingIds, new FutureAccountNotifications(), context);
}
private List<InvoiceItemModelDao> createInvoices(final Iterable<InvoiceModelDao> invoices,
+ final Set<InvoiceTrackingModelDao> trackingIds,
final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context) {
// Track invoices that are being created
@@ -418,6 +421,13 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, adjustedInvoiceId, accountId, context.getUserToken(), context);
}
}
+
+
+ if (trackingIds != null && !trackingIds.isEmpty()) {
+ final InvoiceTrackingSqlDao trackingIdsSqlDao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
+ trackingIdsSqlDao.create(trackingIds, context);
+ }
+
return createdInvoiceItems;
}
});
@@ -1357,6 +1367,17 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
});
}
+ @Override
+ public List<InvoiceTrackingModelDao> getTrackingsByDateRange(final LocalDate startDate, final LocalDate endDate, final InternalCallContext context) {
+ return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<InvoiceTrackingModelDao>>() {
+ @Override
+ public List<InvoiceTrackingModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final InvoiceTrackingSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
+ return transactional.getTrackingsByDateRange(startDate.toDate(), endDate.toDate(), context);
+ }
+ });
+ }
+
// PERF: fetch tags once. See also https://github.com/killbill/killbill/issues/720.
private List<Tag> getInvoicesTags(final InternalTenantContext context) {
return tagInternalApi.getTagsForAccountType(ObjectType.INVOICE, false, context);
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 e7c7790..f14ada8 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
@@ -21,6 +21,7 @@ package org.killbill.billing.invoice.dao;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -39,11 +40,15 @@ import org.killbill.billing.util.entity.dao.EntityDao;
public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceApiException> {
+
void createInvoice(final InvoiceModelDao invoice,
+ final Set<InvoiceTrackingModelDao> trackingIds,
final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context);
- List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoices, final InternalCallContext context);
+ List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoices,
+ final Set<InvoiceTrackingModelDao> trackingIds,
+ final InternalCallContext context);
public void setFutureAccountNotificationsForEmptyInvoice(final UUID accountId, final FutureAccountNotifications callbackDateTimePerSubscriptions,
final InternalCallContext context);
@@ -218,4 +223,7 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
* @throws InvoiceApiException if any unexpected error occurs
*/
List<InvoiceItemModelDao> getInvoiceItemsByParentInvoice(UUID parentInvoiceId, final InternalTenantContext context) throws InvoiceApiException;
+
+ List<InvoiceTrackingModelDao> getTrackingsByDateRange(LocalDate startDate, LocalDate endDate, InternalCallContext context);
+
}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java
new file mode 100644
index 0000000..4991040
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java
@@ -0,0 +1,145 @@
+/*
+ * 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.dao;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.dao.TableName;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
+
+import com.google.common.base.Objects;
+
+public class InvoiceTrackingModelDao extends EntityModelDaoBase implements EntityModelDao<Entity> {
+
+ private String trackingId;
+ private UUID invoiceId;
+ private UUID subscriptionId;
+ private String unitType;
+ private LocalDate recordDate;
+
+ public InvoiceTrackingModelDao() { /* For the DAO mapper */ }
+
+
+ public InvoiceTrackingModelDao(final String trackingId,final UUID invoiceId, final UUID subscriptionId, final String unitType, final LocalDate recordDate) {
+ this(UUIDs.randomUUID(), null, trackingId, invoiceId, subscriptionId, unitType, recordDate);
+ }
+
+ public InvoiceTrackingModelDao(final UUID id, @Nullable final DateTime createdDate, final String trackingId,
+ final UUID invoiceId, final UUID subscriptionId, final String unitType, final LocalDate recordDate) {
+ super(id, createdDate, createdDate);
+ this.trackingId = trackingId;
+ this.invoiceId = invoiceId;
+ this.subscriptionId = subscriptionId;
+ this.unitType = unitType;
+ this.recordDate = recordDate;
+ }
+
+ public String getTrackingId() {
+ return trackingId;
+ }
+
+ public void setTrackingId(final String trackingId) {
+ this.trackingId = trackingId;
+ }
+
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ public void setInvoiceId(final UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ }
+
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ public void setSubscriptionId(final UUID subscriptionId) {
+ this.subscriptionId = subscriptionId;
+ }
+
+ public String getUnitType() {
+ return unitType;
+ }
+
+ public void setUnitType(final String unitType) {
+ this.unitType = unitType;
+ }
+
+ public LocalDate getRecordDate() {
+ return recordDate;
+ }
+
+ public void setRecordDate(final LocalDate recordDate) {
+ this.recordDate = recordDate;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof InvoiceTrackingModelDao)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ final InvoiceTrackingModelDao that = (InvoiceTrackingModelDao) o;
+ return Objects.equal(trackingId, that.trackingId) &&
+ Objects.equal(invoiceId, that.invoiceId) &&
+ Objects.equal(subscriptionId, that.subscriptionId) &&
+ Objects.equal(unitType, that.unitType) &&
+ Objects.equal(recordDate, that.recordDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(super.hashCode(), trackingId, invoiceId, subscriptionId, unitType, recordDate);
+ }
+
+ @Override
+ public String toString() {
+ return "InvoiceTrackingModelDao{" +
+ "trackingId='" + trackingId + '\'' +
+ ", invoiceId=" + invoiceId +
+ ", subscriptionId=" + subscriptionId +
+ ", unitType='" + unitType + '\'' +
+ ", recordDate=" + recordDate +
+ '}';
+ }
+
+ @Override
+ public TableName getTableName() {
+ return TableName.INVOICE_TRACKING_IDS;
+ }
+
+
+ @Override
+ public TableName getHistoryTableName() {
+ return null;
+ }
+
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
new file mode 100644
index 0000000..00979f8
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
@@ -0,0 +1,52 @@
+/*
+ * 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.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.callcontext.InternalTenantContextBinder;
+import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+
+@KillBillSqlDaoStringTemplate
+public interface InvoiceTrackingSqlDao extends EntitySqlDao<InvoiceTrackingModelDao, Entity> {
+
+ @SqlBatch
+ void create(@SmartBindBean Iterable<InvoiceTrackingModelDao> trackings,
+ @InternalTenantContextBinder final InternalCallContext context);
+
+ @SqlQuery
+ List<InvoiceTrackingModelDao> getTrackingsByDateRange(@Bind("startDate") final Date startDate,
+ @Bind("endDate") final Date endDate,
+ @SmartBindBean final InternalTenantContext context);
+
+
+ @SqlQuery
+ List<InvoiceTrackingModelDao> getTrackingsForInvoice(@Bind("invoiceId") final String invoiceId,
+ @SmartBindBean final InternalTenantContext context);
+
+}
+
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 2e46d79..fa8da71 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -36,16 +36,17 @@ 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.InvoiceStatus;
+import org.killbill.billing.invoice.generator.InvoiceItemGenerator.InvoiceGeneratorResult;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
import org.killbill.billing.invoice.model.DefaultInvoice;
import org.killbill.billing.junction.BillingEventSet;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.clock.Clock;
-import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
@@ -77,7 +78,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final Currency targetCurrency,
final InternalCallContext context) throws InvoiceApiException {
if ((events == null) || (events.size() == 0) || events.isAccountAutoInvoiceOff()) {
- return new InvoiceWithMetadata(null, ImmutableMap.<UUID, SubscriptionFutureNotificationDates>of());
+ return new InvoiceWithMetadata(null, ImmutableSet.of(), ImmutableMap.<UUID, SubscriptionFutureNotificationDates>of());
}
validateTargetDate(targetDate, context);
@@ -91,11 +92,11 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates = new HashMap<UUID, SubscriptionFutureNotificationDates>();
- final List<InvoiceItem> fixedAndRecurringItems = recurringInvoiceItemGenerator.generateItems(account, invoice.getId(), events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
- invoice.addInvoiceItems(fixedAndRecurringItems);
+ final InvoiceGeneratorResult fixedAndRecurringItems = recurringInvoiceItemGenerator.generateItems(account, invoice.getId(), events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
+ invoice.addInvoiceItems(fixedAndRecurringItems.getItems());
- final List<InvoiceItem> usageItems = usageInvoiceItemGenerator.generateItems(account, invoice.getId(), events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
- invoice.addInvoiceItems(usageItems);
+ final InvoiceGeneratorResult usageItemsWithTrackingIds = usageInvoiceItemGenerator.generateItems(account, invoice.getId(), events, existingInvoices, adjustedTargetDate, targetCurrency, perSubscriptionFutureNotificationDates, context);
+ invoice.addInvoiceItems(usageItemsWithTrackingIds.getItems());
if (targetInvoiceId != null) {
final Invoice originalInvoice = Iterables.tryFind(existingInvoices, new Predicate<Invoice>() {
@@ -108,7 +109,9 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
invoice.addInvoiceItems(originalInvoice.getInvoiceItems());
}
- return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice, perSubscriptionFutureNotificationDates);
+ return new InvoiceWithMetadata(invoice.getInvoiceItems().isEmpty() ? null : invoice,
+ usageItemsWithTrackingIds.getTrackingIds(),
+ perSubscriptionFutureNotificationDates);
}
private void validateTargetDate(final LocalDate targetDate, final InternalTenantContext context) throws InvoiceApiException {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
index afc6032..9756bff 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
@@ -59,6 +59,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Range;
@@ -82,10 +83,10 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator
this.clock = clock;
}
- public List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
- @Nullable final Iterable<Invoice> existingInvoices, final LocalDate targetDate,
- final Currency targetCurrency, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
- final InternalCallContext internalCallContext) throws InvoiceApiException {
+ public InvoiceGeneratorResult generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
+ @Nullable final Iterable<Invoice> existingInvoices, final LocalDate targetDate,
+ final Currency targetCurrency, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
+ final InternalCallContext internalCallContext) throws InvoiceApiException {
final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription = LinkedListMultimap.<UUID, LocalDate>create();
final AccountItemTree accountItemTree = new AccountItemTree(account.getId(), invoiceId);
@@ -118,7 +119,7 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator
final List<InvoiceItem> resultingItems = accountItemTree.getResultingItemList();
safetyBounds(resultingItems, createdItemsPerDayPerSubscription, internalCallContext);
- return resultingItems;
+ return new InvoiceGeneratorResult(resultingItems, ImmutableSet.of());
}
private void processRecurringBillingEvents(final UUID invoiceId, final UUID accountId, final BillingEventSet events,
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 c5db403..2f1a419 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
@@ -20,6 +20,7 @@ package org.killbill.billing.invoice.generator;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -32,15 +33,38 @@ 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.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.junction.BillingEventSet;
import org.slf4j.Logger;
public abstract class InvoiceItemGenerator {
- public abstract List<InvoiceItem> generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
- @Nullable final Iterable<Invoice> existingInvoices, final LocalDate targetDate,
- final Currency targetCurrency, Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
- final InternalCallContext context) throws InvoiceApiException;
+
+ public abstract InvoiceGeneratorResult generateItems(final ImmutableAccountData account, final UUID invoiceId, final BillingEventSet eventSet,
+ @Nullable final Iterable<Invoice> existingInvoices, final LocalDate targetDate,
+ final Currency targetCurrency, Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDate,
+ final InternalCallContext context) throws InvoiceApiException;
+
+
+ public static class InvoiceGeneratorResult {
+ private final List<InvoiceItem> items;
+ private final Set<TrackingRecordId> trackingIds;
+
+ public InvoiceGeneratorResult(final List<InvoiceItem> items, final Set<TrackingRecordId> trackingIds) {
+ this.items = items;
+ this.trackingIds = trackingIds;
+ }
+
+ public List<InvoiceItem> getItems() {
+ return items;
+ }
+
+ public Set<TrackingRecordId> getTrackingIds() {
+ return trackingIds;
+ }
+ }
+
+
public static class InvoiceItemGeneratorLogger {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
index 9425e0c..cd6e1cd 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceWithMetadata.java
@@ -17,10 +17,9 @@
package org.killbill.billing.invoice.generator;
-import java.math.BigDecimal;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -31,6 +30,7 @@ import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.model.DefaultInvoice;
+import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
@@ -40,9 +40,12 @@ public class InvoiceWithMetadata {
private DefaultInvoice invoice;
- public InvoiceWithMetadata(final DefaultInvoice originalInvoice, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
+ private final Set<TrackingRecordId> trackingIds;
+
+ public InvoiceWithMetadata(final DefaultInvoice originalInvoice, final Set<TrackingRecordId> trackingIds, final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates) {
this.invoice = originalInvoice;
this.perSubscriptionFutureNotificationDates = perSubscriptionFutureNotificationDates;
+ this.trackingIds = trackingIds;
build();
}
@@ -80,6 +83,87 @@ public class InvoiceWithMetadata {
});
}
+ public Set<TrackingRecordId> getTrackingIds() {
+ return trackingIds;
+ }
+
+ public static class TrackingRecordId {
+
+ private final String trackingId;
+ private final UUID invoiceId;
+ private final UUID subscriptionId;
+ private final String unitType;
+ private final LocalDate recordDate;
+
+ public TrackingRecordId(final String trackingId, final UUID invoiceId, final UUID subscriptionId, final String unitType, final LocalDate recordDate) {
+ this.trackingId = trackingId;
+ this.invoiceId = invoiceId;
+ this.subscriptionId = subscriptionId;
+ this.unitType = unitType;
+ this.recordDate = recordDate;
+ }
+
+ public String getTrackingId() {
+ return trackingId;
+ }
+
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ public UUID getSubscriptionId() {
+ return subscriptionId;
+ }
+
+ public LocalDate getRecordDate() {
+ return recordDate;
+ }
+
+ public String getUnitType() {
+ return unitType;
+ }
+
+
+ //
+ // Two records are similar if they were issued from the same usage record {subscriptionId, trackingId, unitType, recordDate}
+ // regardless on which 'invoice' they got attached to.
+ //
+ public boolean isSimilarRecord(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TrackingRecordId)) {
+ return false;
+ }
+ final TrackingRecordId that = (TrackingRecordId) o;
+ return Objects.equal(trackingId, that.trackingId) &&
+ Objects.equal(subscriptionId, that.subscriptionId) &&
+ Objects.equal(unitType, that.unitType) &&
+ Objects.equal(recordDate, that.recordDate);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TrackingRecordId)) {
+ return false;
+ }
+ final TrackingRecordId that = (TrackingRecordId) o;
+ return Objects.equal(trackingId, that.trackingId) &&
+ Objects.equal(invoiceId, that.invoiceId) &&
+ Objects.equal(subscriptionId, that.subscriptionId) &&
+ Objects.equal(unitType, that.unitType) &&
+ Objects.equal(recordDate, that.recordDate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(trackingId, invoiceId, subscriptionId, unitType, recordDate);
+ }
+ }
+
public static class SubscriptionFutureNotificationDates {
private final BillingMode recurringBillingMode;
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 5fd4826..5e7f5c2 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
@@ -18,10 +18,12 @@
package org.killbill.billing.invoice.generator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -39,6 +41,7 @@ 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.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.usage.RawUsageOptimizer;
import org.killbill.billing.invoice.usage.RawUsageOptimizer.RawUsageOptimizerResult;
import org.killbill.billing.invoice.usage.SubscriptionUsageInArrear;
@@ -71,15 +74,16 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
this.invoiceConfig = invoiceConfig;
}
+
@Override
- public List<InvoiceItem> generateItems(final ImmutableAccountData account,
- final UUID invoiceId,
- final BillingEventSet eventSet,
- @Nullable final Iterable<Invoice> existingInvoices,
- final LocalDate targetDate,
- final Currency targetCurrency,
- final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates,
- final InternalCallContext internalCallContext) throws InvoiceApiException {
+ public InvoiceGeneratorResult generateItems(final ImmutableAccountData account,
+ final UUID invoiceId,
+ final BillingEventSet eventSet,
+ @Nullable final Iterable<Invoice> existingInvoices,
+ final LocalDate targetDate,
+ final Currency targetCurrency,
+ final Map<UUID, SubscriptionFutureNotificationDates> perSubscriptionFutureNotificationDates,
+ final InternalCallContext internalCallContext) throws InvoiceApiException {
final Map<UUID, List<InvoiceItem>> perSubscriptionInArrearUsageItems = extractPerSubscriptionExistingInArrearUsageItems(eventSet.getUsages(), existingInvoices);
try {
// Pretty-print the generated invoice items from the junction events
@@ -87,10 +91,11 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
final UsageDetailMode usageDetailMode = invoiceConfig.getItemResultBehaviorMode(internalCallContext);
final LocalDate minBillingEventDate = getMinBillingEventDate(eventSet, internalCallContext);
+ final Set<TrackingRecordId> trackingIds = new HashSet<>();
final List<InvoiceItem> items = Lists.newArrayList();
final Iterator<BillingEvent> events = eventSet.iterator();
- RawUsageOptimizerResult rawUsageOptimizerResult = null;
+ RawUsageOptimizerResult rawUsgRes = null;
List<BillingEvent> curEvents = Lists.newArrayList();
UUID curSubscriptionId = null;
while (events.hasNext()) {
@@ -102,29 +107,31 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
}
// Optimize to do the usage query only once after we know there are indeed some usage items
- if (rawUsageOptimizerResult == null &&
+ if (rawUsgRes == null &&
Iterables.any(event.getUsages(), new Predicate<Usage>() {
@Override
public boolean apply(@Nullable final Usage input) {
return input.getBillingMode() == BillingMode.IN_ARREAR;
}
})) {
- rawUsageOptimizerResult = rawUsageOptimizer.getInArrearUsage(minBillingEventDate, targetDate, Iterables.concat(perSubscriptionInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext);
+ rawUsgRes = rawUsageOptimizer.getInArrearUsage(minBillingEventDate, targetDate, Iterables.concat(perSubscriptionInArrearUsageItems.values()), eventSet.getUsages(), internalCallContext);
}
// None of the billing events report any usage IN_ARREAR sections
- if (rawUsageOptimizerResult == null) {
+ if (rawUsgRes == null) {
continue;
}
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(), usageDetailMode, internalCallContext);
+ final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsgRes.getRawUsage(), rawUsgRes.getExistingTrackingIds(), targetDate, rawUsgRes.getRawUsageStartDate(), usageDetailMode, internalCallContext);
final List<InvoiceItem> usageInArrearItems = perSubscriptionInArrearUsageItems.get(curSubscriptionId);
final SubscriptionUsageInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionUsageInArrear.computeMissingUsageInvoiceItems(usageInArrearItems != null ? usageInArrearItems : ImmutableList.<InvoiceItem>of(), invoiceItemGeneratorLogger);
final List<InvoiceItem> newInArrearUsageItems = subscriptionResult.getInvoiceItems();
items.addAll(newInArrearUsageItems);
+ trackingIds.addAll(subscriptionResult.getTrackingIds());
+
updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
curEvents = Lists.newArrayList();
}
@@ -132,18 +139,18 @@ 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(), usageDetailMode, internalCallContext);
+ final SubscriptionUsageInArrear subscriptionUsageInArrear = new SubscriptionUsageInArrear(account.getId(), invoiceId, curEvents, rawUsgRes.getRawUsage(), rawUsgRes.getExistingTrackingIds(), targetDate, rawUsgRes.getRawUsageStartDate(), usageDetailMode, internalCallContext);
final List<InvoiceItem> usageInArrearItems = perSubscriptionInArrearUsageItems.get(curSubscriptionId);
final SubscriptionUsageInArrearItemsAndNextNotificationDate subscriptionResult = subscriptionUsageInArrear.computeMissingUsageInvoiceItems(usageInArrearItems != null ? usageInArrearItems : ImmutableList.<InvoiceItem>of(), invoiceItemGeneratorLogger);
final List<InvoiceItem> newInArrearUsageItems = subscriptionResult.getInvoiceItems();
items.addAll(newInArrearUsageItems);
+ trackingIds.addAll(subscriptionResult.getTrackingIds());
updatePerSubscriptionNextNotificationUsageDate(curSubscriptionId, subscriptionResult.getPerUsageNotificationDates(), BillingMode.IN_ARREAR, perSubscriptionFutureNotificationDates);
}
-
invoiceItemGeneratorLogger.logItems();
- return items;
+ return new InvoiceGeneratorResult(items, trackingIds);
} catch (final CatalogApiException e) {
throw new InvoiceApiException(e);
}
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 5d6292d..767cd30 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -69,10 +69,12 @@ import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDao;
import org.killbill.billing.invoice.dao.InvoiceModelDaoHelper;
import org.killbill.billing.invoice.dao.InvoiceParentChildModelDao;
+import org.killbill.billing.invoice.dao.InvoiceTrackingModelDao;
import org.killbill.billing.invoice.generator.InvoiceGenerator;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates;
import org.killbill.billing.invoice.generator.InvoiceWithMetadata.SubscriptionFutureNotificationDates.UsageDef;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.DefaultInvoice;
import org.killbill.billing.invoice.model.InvoiceItemFactory;
import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
@@ -214,7 +216,7 @@ public class InvoiceDispatcher {
final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
- commitInvoiceAndSetFutureNotifications(account, null, notificationsBuilder.build(), context);
+ commitInvoiceAndSetFutureNotifications(account, 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()));
@@ -546,7 +548,7 @@ public class InvoiceDispatcher {
log.warn("Ignoring rescheduleDate='{}', delayed scheduling is unsupported in dry-run", rescheduleDate);
} else {
final FutureAccountNotifications futureAccountNotifications = createNextFutureNotificationDate(rescheduleDate, billingEvents, internalCallContext);
- commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
}
return null;
}
@@ -569,7 +571,7 @@ public class InvoiceDispatcher {
final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());
- commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
postEvent(event);
}
return null;
@@ -613,8 +615,14 @@ public class InvoiceDispatcher {
final List<InvoiceItemModelDao> invoiceItemModelDaos = transformToInvoiceModelDao(invoice.getInvoiceItems());
invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
+
+ final Set<InvoiceTrackingModelDao> trackingIds = new HashSet<>();
+ for (TrackingRecordId cur : invoiceWithMetadata.getTrackingIds()) {
+ trackingIds.add(new InvoiceTrackingModelDao(cur.getTrackingId(), cur.getInvoiceId(), cur.getSubscriptionId(), cur.getUnitType(), cur.getRecordDate()));
+ }
+
// Commit invoice on disk
- commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, invoiceModelDao, trackingIds, futureAccountNotifications, internalCallContext);
success = true;
try {
@@ -627,7 +635,7 @@ public class InvoiceDispatcher {
} finally {
// Make sure we always set future notifications in case of errors
if (!isDryRun && !success) {
- commitInvoiceAndSetFutureNotifications(account, null, futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
}
if (isDryRun || success) {
@@ -798,13 +806,22 @@ public class InvoiceDispatcher {
log.info(tmp.toString());
}
+
+ private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
+ final FutureAccountNotifications futureAccountNotifications,
+ final InternalCallContext context) {
+ commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, context);
+ }
+
+
private void commitInvoiceAndSetFutureNotifications(final ImmutableAccountData account,
@Nullable final InvoiceModelDao invoiceModelDao,
+ final Set<InvoiceTrackingModelDao> trackingIds,
final FutureAccountNotifications futureAccountNotifications,
final InternalCallContext context) {
final boolean isThereAnyItemsLeft = invoiceModelDao != null && !invoiceModelDao.getInvoiceItems().isEmpty();
if (isThereAnyItemsLeft) {
- invoiceDao.createInvoice(invoiceModelDao, futureAccountNotifications, context);
+ invoiceDao.createInvoice(invoiceModelDao, trackingIds, futureAccountNotifications, context);
} else {
invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, context);
}
@@ -1104,7 +1121,7 @@ public class InvoiceDispatcher {
List<InvoiceModelDao> invoices = new ArrayList<InvoiceModelDao>();
invoices.add(draftParentInvoice);
log.info("Adding new itemId='{}', amount='{}' on existing DRAFT invoiceId='{}'", parentInvoiceItem.getId(), childInvoiceAmount, draftParentInvoice.getId());
- invoiceDao.createInvoices(invoices, parentContext);
+ invoiceDao.createInvoices(invoices, ImmutableSet.of(), parentContext);
} else {
if (shouldIgnoreChildInvoice(childInvoice, childInvoiceAmount)) {
return;
@@ -1116,7 +1133,7 @@ public class InvoiceDispatcher {
draftParentInvoice.addInvoiceItem(new InvoiceItemModelDao(parentInvoiceItem));
log.info("Adding new itemId='{}', amount='{}' on new DRAFT invoiceId='{}'", parentInvoiceItem.getId(), childInvoiceAmount, draftParentInvoice.getId());
- invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftParentInvoice), parentContext);
+ invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftParentInvoice), ImmutableSet.of(), parentContext);
}
// save parent child invoice relation
@@ -1212,7 +1229,7 @@ public class InvoiceDispatcher {
parentSummaryInvoiceItem.getId(),
null);
parentInvoiceModelDao.addInvoiceItem(new InvoiceItemModelDao(adj));
- invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(parentInvoiceModelDao), parentContext);
+ invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(parentInvoiceModelDao), ImmutableSet.of(), parentContext);
return;
}
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
index a55c0bf..31fb7a7 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalCapacityUsageInArrear.java
@@ -32,6 +32,7 @@ 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.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.invoice.usage.details.UsageCapacityInArrearAggregate;
import org.killbill.billing.invoice.usage.details.UsageInArrearAggregate;
@@ -55,11 +56,12 @@ public class ContiguousIntervalCapacityUsageInArrear extends ContiguousIntervalU
final UUID accountId,
final UUID invoiceId,
final List<RawUsage> rawSubscriptionUsage,
+ final Set<TrackingRecordId> existingTrackingId,
final LocalDate targetDate,
final LocalDate rawUsageStartDate,
final UsageDetailMode usageDetailMode,
final InternalTenantContext internalTenantContext) {
- super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ super(usage, accountId, invoiceId, rawSubscriptionUsage, existingTrackingId, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
}
@Override
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
index 22e812c..0900225 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java
@@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
@@ -36,6 +37,7 @@ 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.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearAggregate;
import org.killbill.billing.invoice.usage.details.UsageConsumableInArrearTierUnitAggregate;
@@ -64,11 +66,12 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
final UUID accountId,
final UUID invoiceId,
final List<RawUsage> rawSubscriptionUsage,
+ final Set<TrackingRecordId> existingTrackingId,
final LocalDate targetDate,
final LocalDate rawUsageStartDate,
final UsageDetailMode usageDetailMode,
final InternalTenantContext internalTenantContext) {
- super(usage, accountId, invoiceId, rawSubscriptionUsage, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
+ super(usage, accountId, invoiceId, rawSubscriptionUsage, existingTrackingId, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
}
@Override
@@ -143,27 +146,25 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
if (usageDetailMode == UsageDetailMode.DETAIL) {
final UsageConsumableInArrearTierUnitAggregate targetTierUnitDetail = fromJson(bi.getItemDetails(), new TypeReference<UsageConsumableInArrearTierUnitAggregate>() {});
- if (!targetTierUnitDetail.getTierUnit().equals(unitType)) {
- continue;
+ if (targetTierUnitDetail.getTierUnit().equals(unitType)) {
+ tierDetails.add(new UsageConsumableInArrearTierUnitAggregate(targetTierUnitDetail.getTier(), targetTierUnitDetail.getTierUnit(), bi.getRate(), targetTierUnitDetail.getQuantity(), bi.getQuantity(), bi.getAmount()));
}
-
- 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 unitAgg : usageDetail.getTierDetails()) {
+ if (unitAgg.getTierUnit().equals(unitType)) {
+ tierDetails.add(unitAgg);
+ }
+ }
}
}
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());
- }
+ 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());
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 d156a15..8bf1ca5 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
@@ -20,6 +20,7 @@ package org.killbill.billing.invoice.usage;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -40,6 +41,7 @@ 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.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.invoice.usage.details.UsageInArrearAggregate;
import org.killbill.billing.junction.BillingEvent;
@@ -53,11 +55,14 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import static org.killbill.billing.invoice.usage.UsageUtils.getCapacityInArrearUnitTypes;
import static org.killbill.billing.invoice.usage.UsageUtils.getConsumableInArrearUnitTypes;
@@ -76,6 +81,7 @@ public abstract class ContiguousIntervalUsageInArrear {
protected final Usage usage;
protected final Set<String> unitTypes;
protected final List<RawUsage> rawSubscriptionUsage;
+ protected final Set<TrackingRecordId> allExistingTrackingIds;
protected final LocalDate targetDate;
protected final UUID accountId;
protected final UUID invoiceId;
@@ -89,6 +95,7 @@ public abstract class ContiguousIntervalUsageInArrear {
final UUID accountId,
final UUID invoiceId,
final List<RawUsage> rawSubscriptionUsage,
+ final Set<TrackingRecordId> existingTrackingIds,
final LocalDate targetDate,
final LocalDate rawUsageStartDate,
final UsageDetailMode usageDetailMode,
@@ -98,6 +105,7 @@ public abstract class ContiguousIntervalUsageInArrear {
this.invoiceId = invoiceId;
this.unitTypes = usage.getUsageType() == UsageType.CAPACITY ? getCapacityInArrearUnitTypes(usage) : getConsumableInArrearUnitTypes(usage);
this.rawSubscriptionUsage = filterInputRawUsage(rawSubscriptionUsage);
+ this.allExistingTrackingIds = existingTrackingIds;
this.targetDate = targetDate;
this.rawUsageStartDate = rawUsageStartDate;
this.internalTenantContext = internalTenantContext;
@@ -107,7 +115,6 @@ public abstract class ContiguousIntervalUsageInArrear {
this.usageDetailMode = usageDetailMode;
}
-
/**
* Builds the transitionTimes associated to that usage section. Those are determined based on billing events for when to start and when to stop,
* the per usage billingPeriod and finally the targetDate.
@@ -182,11 +189,33 @@ public abstract class ContiguousIntervalUsageInArrear {
Preconditions.checkState(isBuilt.get());
if (transitionTimes.size() < 2) {
- return new UsageInArrearItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of(), computeNextNotificationDate());
+ return new UsageInArrearItemsAndNextNotificationDate(ImmutableList.<InvoiceItem>of(), ImmutableSet.of(), computeNextNotificationDate());
}
final List<InvoiceItem> result = Lists.newLinkedList();
- final List<RolledUpUsage> allUsage = getRolledUpUsage();
+
+ final RolledUpUnitsWithTracking allUsageWithTracking = getRolledUpUsage();
+ final List<RolledUpUsage> allUsage = allUsageWithTracking.getUsage();
+
+ final Set<TrackingRecordId> allTrackingIds = allUsageWithTracking.getTrackingIds();
+
+ final Set<TrackingRecordId> existingTrackingIds = extractTrackingIds(allExistingTrackingIds);
+
+
+ final Set<TrackingRecordId> newTrackingIds = Sets.filter(allTrackingIds, new Predicate<TrackingRecordId>() {
+ @Override
+ public boolean apply(final TrackingRecordId allRecord) {
+
+ return ! Iterables.any(existingTrackingIds, new Predicate<TrackingRecordId>() {
+ @Override
+ public boolean apply(final TrackingRecordId existingRecord) {
+ return existingRecord.isSimilarRecord(allRecord);
+ }
+ });
+ }
+ });
+
+
// Each RolledUpUsage 'ru' is for a specific time period and across all units
for (final RolledUpUsage ru : allUsage) {
@@ -207,9 +236,10 @@ public abstract class ContiguousIntervalUsageInArrear {
final UsageInArrearAggregate toBeBilledUsageDetails = getToBeBilledUsageDetails(rolledUpUnits, billedItems, areAllBilledItemsWithDetails);
final BigDecimal toBeBilledUsage = toBeBilledUsageDetails.getAmount();
populateResults(ru.getStart(), ru.getEnd(), billedUsage, toBeBilledUsage, toBeBilledUsageDetails, areAllBilledItemsWithDetails, isPeriodPreviouslyBilled, result);
+
}
final LocalDate nextNotificationDate = computeNextNotificationDate();
- return new UsageInArrearItemsAndNextNotificationDate(result, nextNotificationDate);
+ return new UsageInArrearItemsAndNextNotificationDate(result, newTrackingIds, nextNotificationDate);
}
protected abstract void populateResults(final LocalDate startDate, final LocalDate endDate, final BigDecimal billedUsage, final BigDecimal toBeBilledUsage, final UsageInArrearAggregate toBeBilledUsageDetails, final boolean areAllBilledItemsWithDetails, final boolean isPeriodPreviouslyBilled, final List<InvoiceItem> result) throws InvoiceApiException;
@@ -248,18 +278,17 @@ public abstract class ContiguousIntervalUsageInArrear {
return result;
}
-
@VisibleForTesting
- List<RolledUpUsage> getRolledUpUsage() {
+ RolledUpUnitsWithTracking getRolledUpUsage() {
final List<RolledUpUsage> result = new ArrayList<RolledUpUsage>();
+ final Set<TrackingRecordId> trackingIds = new HashSet<>();
final Iterator<RawUsage> rawUsageIterator = rawSubscriptionUsage.iterator();
if (!rawUsageIterator.hasNext()) {
- return getEmptyRolledUpUsage();
+ return new RolledUpUnitsWithTracking(getEmptyRolledUpUsage(), ImmutableSet.of());
}
-
//
// Skip all items before our first transition date
//
@@ -275,7 +304,7 @@ public abstract class ContiguousIntervalUsageInArrear {
// Optimize path where all raw usage items are outside or our transitionTimes range
if (prevRawUsage == null || prevRawUsage.getDate().compareTo(transitionTimes.get(transitionTimes.size() - 1)) >= 0) {
- return getEmptyRolledUpUsage();
+ return new RolledUpUnitsWithTracking(getEmptyRolledUpUsage(), ImmutableSet.of());
}
//
@@ -293,13 +322,13 @@ public abstract class ContiguousIntervalUsageInArrear {
perRangeUnitToAmount.put(unitType, 0L);
}
-
// Start consuming prevRawUsage element if it exists and falls into the range
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());
perRangeUnitToAmount.put(prevRawUsage.getUnitType(), updatedAmount);
+ trackingIds.add(new TrackingRecordId(prevRawUsage.getTrackingId(), invoiceId, prevRawUsage.getSubscriptionId(), prevRawUsage.getUnitType(), prevRawUsage.getDate()));
prevRawUsage = null;
}
}
@@ -321,6 +350,7 @@ public abstract class ContiguousIntervalUsageInArrear {
final Long currentAmount = perRangeUnitToAmount.get(curRawUsage.getUnitType());
final Long updatedAmount = computeUpdatedAmount(currentAmount, curRawUsage.getAmount());
perRangeUnitToAmount.put(curRawUsage.getUnitType(), updatedAmount);
+ trackingIds.add(new TrackingRecordId(curRawUsage.getTrackingId(), invoiceId, curRawUsage.getSubscriptionId(), curRawUsage.getUnitType(), curRawUsage.getDate()));
}
}
@@ -335,7 +365,7 @@ public abstract class ContiguousIntervalUsageInArrear {
}
prevDate = curDate;
}
- return result;
+ return new RolledUpUnitsWithTracking(result, trackingIds);
}
private List<RolledUpUsage> getEmptyRolledUpUsage() {
@@ -379,7 +409,18 @@ public abstract class ContiguousIntervalUsageInArrear {
});
return ImmutableList.copyOf(filteredList);
}
-
+
+ private Set<TrackingRecordId> extractTrackingIds(final Set<TrackingRecordId> input) {
+
+ return ImmutableSet.copyOf(Iterables.filter(input, new Predicate<TrackingRecordId>() {
+ @Override
+ public boolean apply(final TrackingRecordId input) {
+ return input.getSubscriptionId().equals(getSubscriptionId());
+ }
+ }));
+
+ }
+
/**
* @param filteredUsageForInterval the list of invoiceItem to consider
* @return the price amount that was already billed for that period and usage section (across unitTypes)
@@ -454,15 +495,16 @@ public abstract class ContiguousIntervalUsageInArrear {
return billingEvents.get(0).getCurrency();
}
-
public class UsageInArrearItemsAndNextNotificationDate {
private final List<InvoiceItem> invoiceItems;
private final LocalDate nextNotificationDate;
+ private final Set<TrackingRecordId> trackingIds;
- public UsageInArrearItemsAndNextNotificationDate(final List<InvoiceItem> invoiceItems, final LocalDate nextNotificationDate) {
+ public UsageInArrearItemsAndNextNotificationDate(final List<InvoiceItem> invoiceItems, final Set<TrackingRecordId> trackingIds, final LocalDate nextNotificationDate) {
this.invoiceItems = invoiceItems;
this.nextNotificationDate = nextNotificationDate;
+ this.trackingIds = trackingIds;
}
public List<InvoiceItem> getInvoiceItems() {
@@ -472,6 +514,29 @@ public abstract class ContiguousIntervalUsageInArrear {
public LocalDate getNextNotificationDate() {
return nextNotificationDate;
}
+
+ public Set<TrackingRecordId> getTrackingIds() {
+ return trackingIds;
+ }
+ }
+
+ public static class RolledUpUnitsWithTracking {
+
+ private final List<RolledUpUsage> usage;
+ private final Set<TrackingRecordId> trackingIds;
+
+ public RolledUpUnitsWithTracking(final List<RolledUpUsage> usage, final Set<TrackingRecordId> trackingIds) {
+ this.usage = usage;
+ this.trackingIds = trackingIds;
+ }
+
+ public List<RolledUpUsage> getUsage() {
+ return usage;
+ }
+
+ public Set<TrackingRecordId> getTrackingIds() {
+ return trackingIds;
+ }
}
protected String toJson(final Object usageInArrearAggregate) {
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 1e513ee..62ce29b 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
@@ -30,7 +30,10 @@ import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Usage;
import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.dao.InvoiceDao;
+import org.killbill.billing.invoice.dao.InvoiceTrackingModelDao;
import org.killbill.billing.invoice.generator.InvoiceDateUtils;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.usage.InternalUserApi;
import org.killbill.billing.usage.RawUsage;
@@ -59,11 +62,13 @@ public class RawUsageOptimizer {
private final InternalUserApi usageApi;
private final InvoiceConfig config;
+ private final InvoiceDao invoiceDao;
@Inject
- public RawUsageOptimizer(final InvoiceConfig config, final InternalUserApi usageApi) {
+ public RawUsageOptimizer(final InvoiceConfig config, final InvoiceDao invoiceDao, final InternalUserApi usageApi) {
this.usageApi = usageApi;
this.config = config;
+ this.invoiceDao = invoiceDao;
}
public RawUsageOptimizerResult getInArrearUsage(final LocalDate firstEventStartDate, final LocalDate targetDate, final Iterable<InvoiceItem> existingUsageItems, final Map<String, Usage> knownUsage, final InternalCallContext internalCallContext) {
@@ -71,7 +76,15 @@ public class RawUsageOptimizer {
log.debug("ConsumableInArrear accountRecordId='{}', rawUsageStartDate='{}', firstEventStartDate='{}'",
internalCallContext.getAccountRecordId(), targetStartDate, firstEventStartDate);
final List<RawUsage> rawUsageData = usageApi.getRawUsageForAccount(targetStartDate, targetDate, internalCallContext);
- return new RawUsageOptimizerResult(targetStartDate, rawUsageData);
+
+ final List<InvoiceTrackingModelDao> trackingIds = invoiceDao.getTrackingsByDateRange(targetStartDate, targetDate, internalCallContext);
+ final Set<TrackingRecordId> existingTrackingIds = ImmutableSet.copyOf(Iterables.transform(trackingIds, new Function<InvoiceTrackingModelDao, TrackingRecordId>() {
+ @Override
+ public TrackingRecordId apply(final InvoiceTrackingModelDao input) {
+ return new TrackingRecordId(input.getTrackingId(), input.getInvoiceId(), input.getSubscriptionId(), input.getUnitType(), input.getRecordDate());
+ }
+ }));
+ return new RawUsageOptimizerResult(targetStartDate, rawUsageData, existingTrackingIds);
}
@VisibleForTesting
@@ -153,10 +166,12 @@ public class RawUsageOptimizer {
private final LocalDate rawUsageStartDate;
private final List<RawUsage> rawUsage;
+ private final Set<TrackingRecordId> existingTrackingIds;
- public RawUsageOptimizerResult(final LocalDate rawUsageStartDate, final List<RawUsage> rawUsage) {
+ public RawUsageOptimizerResult(final LocalDate rawUsageStartDate, final List<RawUsage> rawUsage, final Set<TrackingRecordId> existingTrackingIds) {
this.rawUsageStartDate = rawUsageStartDate;
this.rawUsage = rawUsage;
+ this.existingTrackingIds = existingTrackingIds;
}
public LocalDate getRawUsageStartDate() {
@@ -166,5 +181,9 @@ public class RawUsageOptimizer {
public List<RawUsage> getRawUsage() {
return rawUsage;
}
+
+ public Set<TrackingRecordId> getExistingTrackingIds() {
+ return existingTrackingIds;
+ }
}
}
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 1ec2bf6..a9b11f3 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
@@ -37,18 +37,16 @@ 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.generator.InvoiceWithMetadata.TrackingRecordId;
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;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
@@ -80,6 +78,7 @@ public class SubscriptionUsageInArrear {
private final List<BillingEvent> subscriptionBillingEvents;
private final LocalDate targetDate;
private final List<RawUsage> rawSubscriptionUsage;
+ private final Set<TrackingRecordId> existingTrackingIds;
private final LocalDate rawUsageStartDate;
private final InternalTenantContext internalTenantContext;
private final UsageDetailMode usageDetailMode;
@@ -88,6 +87,7 @@ public class SubscriptionUsageInArrear {
final UUID invoiceId,
final List<BillingEvent> subscriptionBillingEvents,
final List<RawUsage> rawUsage,
+ final Set<TrackingRecordId> existingTrackingIds,
final LocalDate targetDate,
final LocalDate rawUsageStartDate,
final UsageDetailMode usageDetailMode,
@@ -106,6 +106,7 @@ public class SubscriptionUsageInArrear {
return input.getSubscriptionId().equals(subscriptionBillingEvents.get(0).getSubscription().getId());
}
}));
+ this.existingTrackingIds = existingTrackingIds;
this.usageDetailMode = usageDetailMode;
}
@@ -125,6 +126,7 @@ public class SubscriptionUsageInArrear {
invoiceItemGeneratorLogger.append(usageInterval, newItemsWithDetailsAndDate.getInvoiceItems());
result.addUsageInArrearItemsAndNextNotificationDate(usageInterval.getUsage().getName(), newItemsWithDetailsAndDate);
+ result.addTrackingIds(newItemsWithDetailsAndDate.getTrackingIds());
}
return result;
}
@@ -158,8 +160,8 @@ public class SubscriptionUsageInArrear {
ContiguousIntervalUsageInArrear existingInterval = inFlightInArrearUsageIntervals.get(usageKey);
if (existingInterval == null) {
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);
+ new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, existingTrackingIds, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext) :
+ new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawSubscriptionUsage, existingTrackingIds, targetDate, rawUsageStartDate, usageDetailMode, internalTenantContext);
inFlightInArrearUsageIntervals.put(usageKey, existingInterval);
}
@@ -202,37 +204,40 @@ public class SubscriptionUsageInArrear {
public class SubscriptionUsageInArrearItemsAndNextNotificationDate {
- private List<InvoiceItem> invoiceItems;
- private Map<String, LocalDate> perUsageNotificationDates;
+ private final List<InvoiceItem> invoiceItems;
+ private final Map<String, LocalDate> perUsageNotificationDates;
+ private final Set<TrackingRecordId> trackingIds;
public SubscriptionUsageInArrearItemsAndNextNotificationDate() {
- this.invoiceItems = null;
- this.perUsageNotificationDates = null;
+ this.invoiceItems = new LinkedList<InvoiceItem>();
+ this.perUsageNotificationDates = new HashMap<String, LocalDate>();
+ this.trackingIds = new HashSet<>();
}
public void addUsageInArrearItemsAndNextNotificationDate(final String usageName, final UsageInArrearItemsAndNextNotificationDate input) {
if (!input.getInvoiceItems().isEmpty()) {
- if (invoiceItems == null) {
- invoiceItems = new LinkedList<InvoiceItem>();
- }
invoiceItems.addAll(input.getInvoiceItems());
-
}
if (input.getNextNotificationDate() != null) {
- if (perUsageNotificationDates == null) {
- perUsageNotificationDates = new HashMap<String, LocalDate>();
- }
perUsageNotificationDates.put(usageName, input.getNextNotificationDate());
}
}
+ public void addTrackingIds(final Set<TrackingRecordId> input) {
+ trackingIds.addAll(input);
+ }
+
public List<InvoiceItem> getInvoiceItems() {
- return invoiceItems != null ? invoiceItems : ImmutableList.<InvoiceItem>of();
+ return invoiceItems;
}
public Map<String, LocalDate> getPerUsageNotificationDates() {
- return perUsageNotificationDates != null ? perUsageNotificationDates : ImmutableMap.<String, LocalDate>of();
+ return perUsageNotificationDates;
+ }
+
+ public Set<TrackingRecordId> getTrackingIds() {
+ return trackingIds;
}
}
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
new file mode 100644
index 0000000..faf56bb
--- /dev/null
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
@@ -0,0 +1,48 @@
+import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
+
+tableName() ::= "invoice_tracking_ids"
+
+tableFields(prefix) ::= <<
+ <prefix>tracking_id
+, <prefix>invoice_id
+, <prefix>subscription_id
+, <prefix>unit_type
+, <prefix>record_date
+, <prefix>created_by
+, <prefix>created_date
+>>
+
+tableValues() ::= <<
+ :trackingId
+, :invoiceId
+, :subscriptionId
+, :unitType
+, :recordDate
+, :userName
+, :createdDate
+>>
+
+getTrackingsByDateRange() ::= <<
+select
+ <allTableFields("")>
+from <tableName()>
+where
+record_date >= :startDate
+and record_date \< :endDate
+and <accountRecordIdField("")> = :accountRecordId
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
+;
+>>
+
+getTrackingsForInvoice() ::= <<
+select
+ <allTableFields("")>
+from <tableName()>
+where
+invoice_id = :invoiceId
+and <accountRecordIdField("")> = :accountRecordId
+<AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
+;
+>>
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 128c664..4fc7087 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -1,5 +1,23 @@
/*! SET default_storage_engine=INNODB */;
+DROP TABLE IF EXISTS invoice_tracking_ids;
+CREATE TABLE invoice_tracking_ids (
+ record_id serial unique,
+ id varchar(36) NOT NULL,
+ tracking_id varchar(128) NOT NULL,
+ invoice_id varchar(36) NOT NULL,
+ subscription_id varchar(36),
+ unit_type varchar(255) NOT NULL,
+ record_date date NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ account_record_id bigint /*! unsigned */ not null,
+ tenant_record_id bigint /*! unsigned */ not null default 0,
+ PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_tracking_tenant_account_date_idx ON invoice_tracking_ids(tenant_record_id, account_record_id, record_date);
+
+
DROP TABLE IF EXISTS invoice_items;
CREATE TABLE invoice_items (
record_id serial unique,
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20181129164135__tracking_ids.sql b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20181129164135__tracking_ids.sql
new file mode 100644
index 0000000..d7b7c18
--- /dev/null
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20181129164135__tracking_ids.sql
@@ -0,0 +1,16 @@
+DROP TABLE IF EXISTS invoice_tracking_ids;
+CREATE TABLE invoice_tracking_ids (
+ record_id serial unique,
+ id varchar(36) NOT NULL,
+ tracking_id varchar(128) NOT NULL,
+ invoice_id varchar(36) NOT NULL,
+ subscription_id varchar(36),
+ unit_type varchar(255) NOT NULL,
+ record_date date NOT NULL,
+ created_by varchar(50) NOT NULL,
+ created_date datetime NOT NULL,
+ account_record_id bigint /*! unsigned */ not null,
+ tenant_record_id bigint /*! unsigned */ not null default 0,
+ PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_tracking_tenant_account_date_idx ON invoice_tracking_ids(tenant_record_id, account_record_id, record_date);
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 32499df..c76d135 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
@@ -25,6 +25,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import org.joda.time.LocalDate;
@@ -62,6 +63,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
@Override
public void createInvoice(final InvoiceModelDao invoice,
+ final Set<InvoiceTrackingModelDao> trackingIds,
final FutureAccountNotifications callbackDateTimePerSubscriptions, final InternalCallContext context) {
synchronized (monitor) {
storeInvoice(invoice, context);
@@ -81,7 +83,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
}
@Override
- public List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoiceModelDaos, final InternalCallContext context) {
+ public List<InvoiceItemModelDao> createInvoices(final List<InvoiceModelDao> invoiceModelDaos, final Set<InvoiceTrackingModelDao> trackingIds, final InternalCallContext context) {
synchronized (monitor) {
final List<InvoiceItemModelDao> createdItems = new LinkedList<InvoiceItemModelDao>();
for (final InvoiceModelDao invoice : invoiceModelDaos) {
@@ -435,4 +437,9 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice,
public List<InvoiceItemModelDao> getInvoiceItemsByParentInvoice(final UUID parentInvoiceId, final InternalTenantContext context) throws InvoiceApiException {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public List<InvoiceTrackingModelDao> getTrackingsByDateRange(final LocalDate startDate, final LocalDate endDate, final InternalCallContext context) {
+ throw new UnsupportedOperationException();
+ }
}
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 bc02e39..a36be45 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
@@ -80,6 +80,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import static org.killbill.billing.invoice.TestInvoiceHelper.FIVE;
import static org.killbill.billing.invoice.TestInvoiceHelper.TEN;
@@ -822,7 +823,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
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, null));
invoiceForExternalCharge.addInvoiceItem(externalCharge);
- final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), context).get(0);
+ final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), ImmutableSet.of(), context).get(0);
InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
@@ -859,7 +860,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
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, null));
draftInvoiceForExternalCharge.addInvoiceItem(externalCharge);
- final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftInvoiceForExternalCharge), context).get(0);
+ final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(draftInvoiceForExternalCharge), ImmutableSet.of(), context).get(0);
InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
@@ -1783,7 +1784,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
invoiceModelDao.getCurrency(),
null);
invoiceModelDao.addInvoiceItem(new InvoiceItemModelDao(invoiceItem));
- return invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), context).get(0);
+ return invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), ImmutableSet.of(), context).get(0);
}
@Test(groups = "slow")
@@ -1815,7 +1816,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
InvoiceItem parentInvoiceItem = new ParentInvoiceItem(UUID.randomUUID(), today, parentInvoice.getId(), parentAccountId, childAccountId, BigDecimal.TEN, account.getCurrency(), "");
parentInvoice.addInvoiceItem(new InvoiceItemModelDao(parentInvoiceItem));
- invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(parentInvoice), context);
+ invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(parentInvoice), ImmutableSet.of(), context);
final InvoiceModelDao parentDraftInvoice = invoiceDao.getParentDraftInvoice(parentAccountId, context);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
new file mode 100644
index 0000000..dbcef7e
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
@@ -0,0 +1,80 @@
+/*
+ * 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.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
+
+ @Test(groups = "slow")
+ public void testBasicTrackingIds() {
+ final InvoiceTrackingSqlDao dao = dbi.onDemand(InvoiceTrackingSqlDao.class);
+
+ LocalDate startRange = new LocalDate(2018, 8, 1);
+ LocalDate endRange = new LocalDate(2018, 11, 23);
+
+ final UUID invoiceId1 = UUID.randomUUID();
+ final UUID invoiceId2 = UUID.randomUUID();
+ final UUID subscriptionId = UUID.randomUUID();
+
+ // Before desired range
+ final InvoiceTrackingModelDao input0 = new InvoiceTrackingModelDao(UUID.randomUUID(), clock.getUTCNow(), "trackingId0", invoiceId1, subscriptionId, "unit", startRange.minusDays(1));
+
+ final InvoiceTrackingModelDao input1 = new InvoiceTrackingModelDao(UUID.randomUUID(), clock.getUTCNow(), "trackingId1", invoiceId1, subscriptionId, "unit", startRange);
+ final InvoiceTrackingModelDao input2 = new InvoiceTrackingModelDao(UUID.randomUUID(), clock.getUTCNow(), "trackingId2", invoiceId1, subscriptionId, "unit", new LocalDate(2018, 8, 5));
+ final InvoiceTrackingModelDao input3 = new InvoiceTrackingModelDao(UUID.randomUUID(), clock.getUTCNow(), "trackingId3", invoiceId2, subscriptionId, "unit", new LocalDate(2018, 9, 1));
+
+ // After desired range
+ final InvoiceTrackingModelDao input4 = new InvoiceTrackingModelDao(UUID.randomUUID(), clock.getUTCNow(), "trackingId4", invoiceId1, subscriptionId, "unit", endRange);
+
+ final List<InvoiceTrackingModelDao> inputs = new ArrayList<>();
+ inputs.add(input0);
+ inputs.add(input1);
+ inputs.add(input2);
+ inputs.add(input3);
+ inputs.add(input4);
+
+ dao.create(inputs, internalCallContext);
+
+ final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+ Assert.assertEquals(result.size(), 3);
+
+ Assert.assertEquals(result.get(0).getTrackingId(), "trackingId1");
+ Assert.assertEquals(result.get(0).getInvoiceId(), invoiceId1);
+ Assert.assertEquals(result.get(0).getRecordDate(), startRange);
+ Assert.assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
+
+ Assert.assertEquals(result.get(1).getTrackingId(), "trackingId2");
+ Assert.assertEquals(result.get(1).getInvoiceId(), invoiceId1);
+ Assert.assertEquals(result.get(1).getRecordDate(), new LocalDate(2018, 8, 5));
+ Assert.assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
+
+ Assert.assertEquals(result.get(2).getTrackingId(), "trackingId3");
+ Assert.assertEquals(result.get(2).getInvoiceId(), invoiceId2);
+ Assert.assertEquals(result.get(2).getRecordDate(), new LocalDate(2018, 9, 1));
+ Assert.assertEquals(result.get(2).getSubscriptionId(), subscriptionId);
+
+ }
+}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
index 68fcade..e95e5db 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
@@ -347,7 +347,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate.plusMonths(threshold),
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext).size(), 1);
+ internalCallContext).getItems().size(), 1);
// Simulate a big catch-up on that day
for (int i = threshold; i < 2 * threshold; i++) {
@@ -377,7 +377,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate.plusMonths(2 * threshold),
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -436,7 +436,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
// There will be one proposed, but because it will match one of ones in the existing list and we don't repair, it won't be returned
assertEquals(generatedItems.size(), 0);
}
@@ -497,7 +497,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -557,7 +557,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
assertEquals(generatedItems.size(), 2);
assertTrue(generatedItems.get(0) instanceof RecurringInvoiceItem);
assertEquals(generatedItems.get(0).getStartDate(), new LocalDate("2016-01-01"));
@@ -636,7 +636,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
// Maybe we could auto-fix-it one day?
// assertEquals(generatedItems.size(), 1);
@@ -713,7 +713,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
assertEquals(generatedItems.size(), 1);
assertTrue(generatedItems.get(0) instanceof RecurringInvoiceItem);
assertEquals(generatedItems.get(0).getStartDate(), new LocalDate("2016-01-01"));
@@ -793,7 +793,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -863,7 +863,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -899,7 +899,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -936,7 +936,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -992,7 +992,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
@@ -1081,7 +1081,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
assertTrue(generatedItems.isEmpty());
}
@@ -1166,7 +1166,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
assertTrue(generatedItems.isEmpty());
}
@@ -1252,7 +1252,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
startDate,
account.getCurrency(),
new HashMap<UUID, SubscriptionFutureNotificationDates>(),
- internalCallContext);
+ internalCallContext).getItems();
fail();
} catch (final InvoiceApiException e) {
assertEquals(e.getCode(), ErrorCode.UNEXPECTED_ERROR.getCode());
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java
index ef9d691..d20db05 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceWithMetadata.java
@@ -46,6 +46,7 @@ import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableSet;
public class TestInvoiceWithMetadata extends InvoiceTestSuiteNoDB {
@@ -122,7 +123,7 @@ public class TestInvoiceWithMetadata extends InvoiceTestSuiteNoDB {
perSubscriptionFutureNotificationDates.put(subscription.getId(), subscriptionFutureNotificationDates);
- final InvoiceWithMetadata invoiceWithMetadata = new InvoiceWithMetadata(originalInvoice, perSubscriptionFutureNotificationDates);
+ final InvoiceWithMetadata invoiceWithMetadata = new InvoiceWithMetadata(originalInvoice, ImmutableSet.of(), perSubscriptionFutureNotificationDates);
// We generate an invoice with one item, invoicing for $0
final Invoice resultingInvoice = invoiceWithMetadata.getInvoice();
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
index 7d0c111..98f4fad 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/InvoiceTestUtils.java
@@ -47,6 +47,7 @@ import org.mockito.Mockito;
import org.testng.Assert;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
public class InvoiceTestUtils {
@@ -108,7 +109,7 @@ public class InvoiceTestUtils {
final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
invoiceModelDao.addInvoiceItems(invoiceModelItems);
- invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), internalCallContext);
+ invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), ImmutableSet.of(), internalCallContext);
return invoice;
}
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 b994b58..bdbc878 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -60,6 +60,7 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
@@ -197,7 +198,7 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
invoiceModelDao.addInvoiceItem(invoiceItemModelDao1);
invoiceModelDao.addInvoiceItem(invoiceItemModelDao2);
- invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), context);
+ invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceModelDao), ImmutableSet.of(), context);
try {
dispatcher.processAccountFromNotificationOrBusEvent(accountId, target, new DryRunFutureDateArguments(), false, context);
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 a87decb..ffc9602 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
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -35,6 +36,7 @@ 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.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
import org.killbill.billing.invoice.model.UsageInvoiceItem;
import org.killbill.billing.invoice.usage.ContiguousIntervalUsageInArrear.UsageInArrearItemsAndNextNotificationDate;
@@ -45,8 +47,6 @@ import org.killbill.billing.usage.RawUsage;
import org.killbill.billing.usage.api.RolledUpUnit;
import org.killbill.billing.usage.api.svcs.DefaultRawUsage;
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.fasterxml.jackson.core.type.TypeReference;
@@ -56,6 +56,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBase {
@@ -132,10 +133,10 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final LocalDate targetDate = new LocalDate(2014, 03, 20);
final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, ImmutableList.<RawUsage>of(), targetDate, false,
- createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
- BillingPeriod.MONTHLY,
- Collections.<Usage>emptyList())
- );
+ createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
+ BillingPeriod.MONTHLY,
+ Collections.<Usage>emptyList())
+ );
// Tier 1 (both units from tier 1)
UsageCapacityInArrearAggregate result = intervalCapacityInArrear.computeToBeBilledCapacityInArrear(ImmutableList.<RolledUpUnit>of(new DefaultRolledUpUnit("unit1", 100L),
new DefaultRolledUpUnit("unit2", 1000L),
@@ -164,6 +165,7 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
assertTrue(result.getAmount().compareTo(new BigDecimal("30.0")) == 0);
}
+
@Test(groups = "fast")
public void testComputeMissingItems() throws CatalogApiException, InvoiceApiException {
@@ -172,22 +174,23 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final LocalDate endDate = new LocalDate(2014, 05, 15);
final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
+
//
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit1", 130L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit1", 271L, "tracking-2"));
// 1 items for unit2
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 24), "unit2", 10L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 24), "unit2", 10L, "tracking-1"));
//
// Second period: firstBCDDate - endDate
//
// 1 items unit1
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit1", 199L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit1", 199L, "tracking-4"));
// 1 items unit2
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit2", 20L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit2", 20L, "tracking-5"));
final DefaultUnit unit1 = new DefaultUnit().setName("unit1");
final DefaultLimit limit1 = new DefaultLimit().setUnit(unit1).setMax((double) -1);
@@ -216,6 +219,8 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final List<InvoiceItem> result = usageResult.getInvoiceItems();
assertEquals(result.size(), 2);
+ final Set<TrackingRecordId> trackingIds = usageResult.getTrackingIds();
+ checkTrackingIds(rawUsages, usageResult.getTrackingIds());
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);
@@ -254,8 +259,8 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L, "tracking-2"));
List<InvoiceItem> result = produceInvoiceItems(rawUsages, usageDetailMode, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -279,8 +284,8 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-2"));
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()));
@@ -304,8 +309,8 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L, "tracking-3"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-3"));
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()));
@@ -336,10 +341,9 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
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
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 60L, "tracking-1")); // tier 3
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 200L, "tracking-1")); // tier 2
final List<UsageInArrearTierUnitDetail> existingUsage = ImmutableList.of(existingFooUsageTier1, existingBarUsageTier2);
@@ -402,6 +406,9 @@ public class TestContiguousIntervalCapacityInArrear extends TestUsageInArrearBas
final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = createContiguousIntervalCapacityInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
final UsageInArrearItemsAndNextNotificationDate usageResult = intervalCapacityInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
+
+ checkTrackingIds(rawUsages, usageResult.getTrackingIds());
+
final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
@Override
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 cb532bd..6ea9a06 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
@@ -354,10 +354,10 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "unit", 130L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "unit", 271L, "tracking-1"));
// 1 items for firstBCDDate - endDate
- rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit", 199L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 04, 15), "unit", 199L, "tracking-2"));
final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(block);
@@ -378,6 +378,9 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
invoiceItems.add(ii2);
final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(invoiceItems);
+ checkTrackingIds(rawUsages, usageResult.getTrackingIds());
+
+
final List<InvoiceItem> result = usageResult.getInvoiceItems();
assertEquals(result.size(), 2);
@@ -445,27 +448,27 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate targetDate = t3;
// Prev t0
- final RawUsage raw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 01), "unit", 12L);
+ final RawUsage raw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 01), "unit", 12L, "tracking-1");
// t0 - t1
- final RawUsage raw2 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 15), "unit", 6L);
- final RawUsage raw3 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 25), "unit", 4L);
+ final RawUsage raw2 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 15), "unit", 6L, "tracking-1");
+ final RawUsage raw3 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 25), "unit", 4L, "tracking-1");
// t1 - t2 nothing
// t2 - t3
- final RawUsage raw4 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 15), "unit", 13L);
- final RawUsage oraw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 21), "unit2", 21L);
- final RawUsage raw5 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 31), "unit", 7L);
+ final RawUsage raw4 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 15), "unit", 13L, "tracking-2");
+ final RawUsage oraw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 21), "unit2", 21L, "tracking-2");
+ final RawUsage raw5 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 05, 31), "unit", 7L, "tracking-2");
// after t3
- final RawUsage raw6 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 06, 15), "unit", 100L);
+ final RawUsage raw6 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 06, 15), "unit", 100L, "tracking-3");
final List<RawUsage> rawUsage = ImmutableList.of(raw1, raw2, raw3, raw4, oraw1, raw5, raw6);
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsage, targetDate, true, eventT0, eventT1, eventT2, eventT3);
- final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage();
+ final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage().getUsage();
Assert.assertEquals(unsortedRolledUpUsage.size(), 3);
final List<RolledUpUsage> rolledUpUsage = TEST_ROLLED_UP_FIRST_USAGE_ORDERING.sortedCopy(unsortedRolledUpUsage);
@@ -506,7 +509,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final LocalDate rawUsageStartDate = new LocalDate(2015, 10, 16);
final List<RawUsage> rawUsages = new ArrayList<RawUsage>();
- rawUsages.add(new DefaultRawUsage(subscriptionId, startDate, "unit", 130L));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, startDate, "unit", 130L, "tracking-1"));
final DefaultTieredBlock block = createDefaultTieredBlock("unit", 100, 10, BigDecimal.ONE);
final DefaultTier tier = createDefaultTierWithBlocks(block);
@@ -516,8 +519,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
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);
+ new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, EMPTY_EXISTING_TRACKING_IDS, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext) :
+ new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, EMPTY_EXISTING_TRACKING_IDS, targetDate, rawUsageStartDate, usageDetailMode, internalCallContext);
intervalConsumableInArrear.addBillingEvent(event1);
intervalConsumableInArrear.addBillingEvent(event2);
@@ -555,8 +558,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L, "tracking-1"));
List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -580,8 +583,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-2"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-2"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -611,8 +614,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L, "tracking-3"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-4"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -657,8 +660,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L, "tracking-1"));
List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 2);
@@ -673,8 +676,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-1"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 3);
@@ -693,8 +696,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 75L, "tracking-2"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-2"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.ALL_TIERS, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 5);
@@ -725,8 +728,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L, "tracking-1"));
List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -749,8 +752,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-2"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-2"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -774,8 +777,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L, "tracking-3"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-3"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.AGGREGATE, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 1);
@@ -802,8 +805,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-1"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 99L, "tracking-2"));
List<InvoiceItem> result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 2);
@@ -818,8 +821,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 5L, "tracking-3"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-4"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 2);
@@ -834,8 +837,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 76L, "tracking-5"));
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 101L, "tracking-6"));
result = produceInvoiceItems(rawUsages, TierBlockPolicy.TOP_TIER, UsageDetailMode.DETAIL, ImmutableList.<InvoiceItem>of());
assertEquals(result.size(), 2);
@@ -871,8 +874,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 50L /* already built */ + 20L, "tracking-1")); // tier 3
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L, "tracking-1")); // tier 2
final List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, productName, planName, phaseName, usageName, new LocalDate(2014, 03, 20), new LocalDate(2014, 04, 15), new BigDecimal("570.00"), null, currency, null, existingUsageJson);
@@ -932,8 +935,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// 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
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 20), "FOO", 50L /* already built */ + 20L, "tracking-1")); // tier 3
+ rawUsages.add(new DefaultRawUsage(subscriptionId, new LocalDate(2014, 03, 21), "BAR", 80L /* already built */ + 120L, "tracking-1")); // 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"));
@@ -1022,14 +1025,14 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
// Prev t0
- final RawUsage raw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 01), "unit", 12L);
+ final RawUsage raw1 = new DefaultRawUsage(subscriptionId, new LocalDate(2015, 03, 01), "unit", 12L, "tracking-1");
final List<RawUsage> rawUsage = ImmutableList.of(raw1);
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsage, targetDate, true, eventT0, eventT1);
- final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage();
+ final List<RolledUpUsage> unsortedRolledUpUsage = intervalConsumableInArrear.getRolledUpUsage().getUsage();
assertEquals(unsortedRolledUpUsage.size(), 2);
assertEquals(unsortedRolledUpUsage.get(0).getRolledUpUnits().size(), 1);
assertEquals(unsortedRolledUpUsage.get(0).getRolledUpUnits().get(0).getAmount().longValue(), 0L);
@@ -1065,6 +1068,9 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
final ContiguousIntervalUsageInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, rawUsages, targetDate, true, usageDetailMode, event1, event2);
final UsageInArrearItemsAndNextNotificationDate usageResult = intervalConsumableInArrear.computeMissingItemsAndNextNotificationDate(existingItems);
+
+ checkTrackingIds(rawUsages, usageResult.getTrackingIds());
+
final List<InvoiceItem> rawResults = usageResult.getInvoiceItems();
final List<InvoiceItem> result = ImmutableList.copyOf(Iterables.filter(rawResults, new Predicate<InvoiceItem>() {
@Override
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 c9fe78b..c9925af 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
@@ -34,6 +34,7 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import static org.testng.Assert.assertEquals;
@@ -70,7 +71,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), usageDetailMode, internalCallContext);
+ final SubscriptionUsageInArrear foo = new SubscriptionUsageInArrear(accountId, invoiceId, billingEvents, ImmutableList.<RawUsage>of(), ImmutableSet.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 4215728..5daaec1 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
@@ -18,7 +18,11 @@
package org.killbill.billing.invoice.usage;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import org.joda.time.DateTime;
@@ -41,6 +45,7 @@ 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.InvoiceTestSuiteNoDB;
+import org.killbill.billing.invoice.generator.InvoiceWithMetadata.TrackingRecordId;
import org.killbill.billing.junction.BillingEvent;
import org.killbill.billing.subscription.api.SubscriptionBase;
import org.killbill.billing.usage.RawUsage;
@@ -49,8 +54,18 @@ import org.killbill.billing.util.jackson.ObjectMapper;
import org.mockito.Mockito;
import org.testng.annotations.BeforeClass;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
+ protected static final Set<TrackingRecordId> EMPTY_EXISTING_TRACKING_IDS = ImmutableSet.of();
+
protected int BCD;
protected UUID accountId;
protected UUID bundleId;
@@ -63,7 +78,6 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
protected String usageName;
protected ObjectMapper objectMapper;
-
@BeforeClass(groups = "fast")
protected void beforeClass() throws Exception {
if (hasFailed()) {
@@ -85,13 +99,12 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
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);
+ final ContiguousIntervalCapacityUsageInArrear intervalCapacityInArrear = new ContiguousIntervalCapacityUsageInArrear(usage, accountId, invoiceId, rawUsages, EMPTY_EXISTING_TRACKING_IDS, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
for (final BillingEvent event : events) {
intervalCapacityInArrear.addBillingEvent(event);
}
@@ -99,13 +112,12 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
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 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);
+ final ContiguousIntervalConsumableUsageInArrear intervalConsumableInArrear = new ContiguousIntervalConsumableUsageInArrear(usage, accountId, invoiceId, rawUsages, EMPTY_EXISTING_TRACKING_IDS, targetDate, new LocalDate(events[0].getEffectiveDate()), detailMode, internalCallContext);
for (final BillingEvent event : events) {
intervalConsumableInArrear.addBillingEvent(event);
}
@@ -176,7 +188,6 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
Mockito.when(result.getEffectiveDate()).thenReturn(effectiveDate);
Mockito.when(result.getBillingPeriod()).thenReturn(billingPeriod);
-
final Account account = Mockito.mock(Account.class);
Mockito.when(account.getId()).thenReturn(accountId);
@@ -201,4 +212,54 @@ public abstract class TestUsageInArrearBase extends InvoiceTestSuiteNoDB {
return result;
}
+ //
+ // Each input `RawUsage` should end up creating one TrackingRecordId
+ // Regardless of how test records trackingId -- grouped in one, or multiple calls-- and regardless of test matrix, the logics below should remain true.
+ //
+ protected void checkTrackingIds(final List<RawUsage> rawUsages, final Set<TrackingRecordId> trackingRecords) {
+
+ // Verify we have same input and output
+ assertEquals(rawUsages.size(), trackingRecords.size());
+
+ final Map<String, List<RawUsage>> trackingIdMapping = new HashMap<>();
+ for (final RawUsage u : rawUsages) {
+ if (!trackingIdMapping.containsKey(u.getTrackingId())) {
+ trackingIdMapping.put(u.getTrackingId(), new ArrayList<>());
+ }
+ trackingIdMapping.get(u.getTrackingId()).add(u);
+ }
+
+ final Set<String> trackingIds = ImmutableSet.copyOf(Iterables.transform(trackingRecords, new Function<TrackingRecordId, String>() {
+ @Override
+ public String apply(final TrackingRecordId input) {
+ return input.getTrackingId();
+ }
+ }));
+
+ // Verify the per trackingId input matches the per trackingId output
+ assertEquals(trackingIdMapping.size(), trackingIds.size());
+
+ for (final String id : trackingIdMapping.keySet()) {
+
+ final List<RawUsage> rawUsageForId = trackingIdMapping.get(id);
+ for (RawUsage u : rawUsageForId) {
+
+ final TrackingRecordId found = Iterables.tryFind(trackingRecords, new Predicate<TrackingRecordId>() {
+ @Override
+ public boolean apply(final TrackingRecordId input) {
+ return input.getTrackingId().equals(u.getTrackingId()) &&
+ input.getRecordDate().equals(u.getDate()) &&
+ input.getUnitType().equals(u.getUnitType());
+ }
+ }).orNull();
+ assertNotNull(found, "Cannot find tracking Id " + u.getTrackingId());
+
+ assertEquals(found.getSubscriptionId(), subscriptionId);
+ assertEquals(found.getInvoiceId(), invoiceId);
+ assertEquals(found.getRecordDate(), u.getDate());
+ assertEquals(found.getUnitType(), u.getUnitType());
+ }
+ }
+ }
+
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultInternalUserApi.java b/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultInternalUserApi.java
index e2de011..61e7c19 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultInternalUserApi.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultInternalUserApi.java
@@ -49,7 +49,7 @@ public class DefaultInternalUserApi implements InternalUserApi {
@Nullable
@Override
public RawUsage apply(final RolledUpUsageModelDao input) {
- return new DefaultRawUsage(input.getSubscriptionId(), input.getRecordDate(), input.getUnitType(), input.getAmount());
+ return new DefaultRawUsage(input.getSubscriptionId(), input.getRecordDate(), input.getUnitType(), input.getAmount(), input.getTrackingId());
}
}));
}
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultRawUsage.java b/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultRawUsage.java
index f7a50f0..fe61b55 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultRawUsage.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/svcs/DefaultRawUsage.java
@@ -28,12 +28,14 @@ public class DefaultRawUsage implements RawUsage {
private final LocalDate recordDate;
private final String unitType;
private final Long amount;
+ private final String trackingId;
- public DefaultRawUsage(final UUID subscriptionId, final LocalDate recordDate, final String unitType, final Long amount) {
+ public DefaultRawUsage(final UUID subscriptionId, final LocalDate recordDate, final String unitType, final Long amount, final String trackingId) {
this.subscriptionId = subscriptionId;
this.recordDate = recordDate;
this.unitType = unitType;
this.amount = amount;
+ this.trackingId = trackingId;
}
@Override
@@ -57,12 +59,18 @@ public class DefaultRawUsage implements RawUsage {
}
@Override
+ public String getTrackingId() {
+ return trackingId;
+ }
+
+ @Override
public String toString() {
final StringBuilder sb = new StringBuilder("DefaultRawUsage{");
sb.append("subscriptionId=").append(subscriptionId);
sb.append(", recordDate=").append(recordDate);
sb.append(", unitType='").append(unitType).append('\'');
sb.append(", amount=").append(amount);
+ sb.append(", trackingId=").append(trackingId);
sb.append('}');
return sb.toString();
}
diff --git a/util/src/main/java/org/killbill/billing/util/dao/TableName.java b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
index f1f51a0..5e6e068 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/TableName.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
@@ -35,6 +35,7 @@ public enum TableName {
INVOICE_ITEMS("invoice_items", ObjectType.INVOICE_ITEM),
INVOICE_PAYMENTS("invoice_payments", ObjectType.INVOICE_PAYMENT),
INVOICES("invoices", ObjectType.INVOICE),
+ INVOICE_TRACKING_IDS("invoice_tracking_ids"),
INVOICE_PARENT_CHILDREN("invoice_parent_children"),
NODE_INFOS("node_infos"),
PAYMENT_ATTEMPT_HISTORY("payment_attempt_history"),
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java
index 69d6647..47d50a1 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java
@@ -23,7 +23,6 @@ import org.joda.time.DateTime;
import org.killbill.billing.entity.EntityBase;
public class EntityModelDaoBase extends EntityBase {
-
private Long recordId;
private Long accountRecordId;
private Long tenantRecordId;