Details
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 fa9ec41..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
@@ -288,7 +288,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
final List<UsageRecord> bp1StoneRecords2 = new ArrayList();
bp1StoneRecords2.add(new UsageRecord(new LocalDate(2012, 4, 23), 10L));
- // Outside of range for this period -> It's tracking ID spreads across 2 invoices
+ // 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);
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 fa567d6..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
@@ -88,6 +88,7 @@ public class InvoiceWithMetadata {
}
public static class TrackingRecordId {
+
private final String trackingId;
private final UUID invoiceId;
private final UUID subscriptionId;
@@ -122,6 +123,25 @@ public class InvoiceWithMetadata {
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) {
@@ -131,13 +151,8 @@ public class InvoiceWithMetadata {
return false;
}
final TrackingRecordId that = (TrackingRecordId) o;
- // !!! Exclude invoiceId on purpose.
- //
- // The Set methods (Sets.difference) is used to exclude usage record already invoiced (on a specified invoiceId),
- // by comparing 2 TrackingIds items with different invoiceId
- //
return Objects.equal(trackingId, that.trackingId) &&
- //Objects.equal(invoiceId, that.invoiceId) &&
+ Objects.equal(invoiceId, that.invoiceId) &&
Objects.equal(subscriptionId, that.subscriptionId) &&
Objects.equal(unitType, that.unitType) &&
Objects.equal(recordDate, that.recordDate);
@@ -145,8 +160,7 @@ public class InvoiceWithMetadata {
@Override
public int hashCode() {
- // !!! Exclude invoiceId on purpose - see comment above
- return Objects.hashCode(trackingId, subscriptionId, unitType, recordDate);
+ return Objects.hashCode(trackingId, invoiceId, subscriptionId, unitType, recordDate);
}
}
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 12296d5..271b15c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -206,7 +206,7 @@ public class InvoiceDispatcher {
final ImmutableAccountData account = accountApi.getImmutableAccountDataById(accountId, context);
- commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), notificationsBuilder.build(), context);
+ commitInvoiceAndSetFutureNotifications(account, notificationsBuilder.build(), context);
} catch (final SubscriptionBaseApiException e) {
log.warn("Failed handling SubscriptionBase change.",
@@ -539,7 +539,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, ImmutableSet.of(), futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
}
return null;
}
@@ -562,7 +562,7 @@ public class InvoiceDispatcher {
final BusInternalEvent event = new DefaultNullInvoiceEvent(accountId, clock.getUTCToday(),
internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId(), internalCallContext.getUserToken());
- commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
postEvent(event);
}
return null;
@@ -626,7 +626,7 @@ public class InvoiceDispatcher {
} finally {
// Make sure we always set future notifications in case of errors
if (!isDryRun && !success) {
- commitInvoiceAndSetFutureNotifications(account, null, ImmutableSet.of(), futureAccountNotifications, internalCallContext);
+ commitInvoiceAndSetFutureNotifications(account, futureAccountNotifications, internalCallContext);
}
if (isDryRun || success) {
@@ -797,6 +797,14 @@ 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,
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 47ea11f..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
@@ -55,6 +55,7 @@ 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;
@@ -200,7 +201,20 @@ public abstract class ContiguousIntervalUsageInArrear {
final Set<TrackingRecordId> existingTrackingIds = extractTrackingIds(allExistingTrackingIds);
- final Set<TrackingRecordId> newTrackingIds = Sets.difference(allTrackingIds, existingTrackingIds);
+
+ 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) {