killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java 9(+0 -9)
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java 100(+100 -0)
invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableUsageInArrear.java 8(+4 -4)
junction/pom.xml 3(+1 -2)
Details
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
index dfbe4f6..7af38bf 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingEvent.java
@@ -33,11 +33,10 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
public interface BillingEvent extends Comparable<BillingEvent> {
-
/**
* @return the billCycleDay in the account timezone as seen for that subscription at that time
- * <p/>
- * Note: The billCycleDay may come from the Account, or the bundle or the subscription itself
+ * <p/>
+ * Note: The billCycleDay may come from the Account, or the bundle or the subscription itself
*/
int getBillCycleDayLocal();
@@ -97,9 +96,13 @@ public interface BillingEvent extends Comparable<BillingEvent> {
Long getTotalOrdering();
/**
- *
* @return the list of {@code Usage} section
*/
List<Usage> getUsages();
-}
+ /**
+ *
+ * @return the catalog version (effective date) associated with this billing event.
+ */
+ public DateTime getCatalogEffectiveDate();
+}
\ No newline at end of file
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 f10a98c..9666370 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
@@ -110,12 +110,17 @@ import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimelineAp
import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApi;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.tenant.api.TenantUserApi;
+import org.killbill.billing.usage.api.SubscriptionUsageRecord;
+import org.killbill.billing.usage.api.UnitUsageRecord;
+import org.killbill.billing.usage.api.UsageApiException;
+import org.killbill.billing.usage.api.UsageRecord;
import org.killbill.billing.usage.api.UsageUserApi;
import org.killbill.billing.util.api.RecordIdApi;
import org.killbill.billing.util.api.TagApiException;
import org.killbill.billing.util.api.TagDefinitionApiException;
import org.killbill.billing.util.api.TagUserApi;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.config.definition.InvoiceConfig;
import org.killbill.billing.util.config.definition.PaymentConfig;
import org.killbill.billing.util.dao.NonEntityDao;
@@ -913,6 +918,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
return result;
}
+ protected void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) throws UsageApiException {
+ final List<UsageRecord> usageRecords = new ArrayList<UsageRecord>();
+ usageRecords.add(new UsageRecord(startDate, amount));
+ final List<UnitUsageRecord> unitUsageRecords = new ArrayList<UnitUsageRecord>();
+ unitUsageRecords.add(new UnitUsageRecord(unitType, usageRecords));
+ final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, UUID.randomUUID().toString(), unitUsageRecords);
+ usageUserApi.recordRolledUpUsage(record, context);
+ }
+
+
protected static class TestDryRunArguments implements DryRunArguments {
private final DryRunType dryRunType;
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 b4a65ac..f134664 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
@@ -255,13 +255,4 @@ public class TestConsumableInArrear extends TestIntegrationBase {
final DateTime thirdExpectedCTD = account.getReferenceTime().withMonthOfYear(7).withDayOfMonth(1);
Assert.assertEquals(subscriptionBaseInternalApiApi.getSubscriptionFromId(bpSubscription.getId(), internalCallContext).getChargedThroughDate().compareTo(thirdExpectedCTD), 0);
}
-
- private void setUsage(final UUID subscriptionId, final String unitType, final LocalDate startDate, final Long amount, final CallContext context) throws UsageApiException {
- final List<UsageRecord> usageRecords = new ArrayList<UsageRecord>();
- usageRecords.add(new UsageRecord(startDate, amount));
- final List<UnitUsageRecord> unitUsageRecords = new ArrayList<UnitUsageRecord>();
- unitUsageRecords.add(new UnitUsageRecord(unitType, usageRecords));
- final SubscriptionUsageRecord record = new SubscriptionUsageRecord(subscriptionId, UUID.randomUUID().toString(), unitUsageRecords);
- usageUserApi.recordRolledUpUsage(record, context);
- }
}
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
new file mode 100644
index 0000000..c49ef93
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration.usage;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.integration.TestIntegrationBase;
+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.InvoiceItemType;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
+
+ @Override
+ protected KillbillConfigSource getConfigSource() {
+ return super.getConfigSource(null, ImmutableMap.of("org.killbill.catalog.uri", "catalogs/testInArrearWithCatalogVersions"));
+ }
+
+ @Test(groups = "slow")
+ public void testWithNoUsageInPeriodAndOldUsage() throws Exception {
+ // 30 days month
+ clock.setDay(new LocalDate(2016, 4, 1));
+
+ final AccountData accountData = getAccountData(1);
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("electricity-monthly");
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+ final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec), null, null, null, false, true, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ setUsage(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 1), 143L, callContext);
+ setUsage(entitlementId, "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")));
+
+ setUsage(entitlementId, "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
+ final Entitlement bp = entitlementApi.getEntitlementForId(entitlementId, callContext);
+
+ bp.changePlanWithDate(new DefaultEntitlementSpecifier(spec), new LocalDate("2016-05-09"), null, callContext);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDays(8);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 9), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+
+
+ setUsage(entitlementId, "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,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 9), new LocalDate(2016, 6, 1), InvoiceItemType.USAGE, new BigDecimal("250.00")));
+
+
+ }
+}
\ No newline at end of file
diff --git a/beatrix/src/test/resources/catalogs/testInArrearWithCatalogVersions/Utility-v1.xml b/beatrix/src/test/resources/catalogs/testInArrearWithCatalogVersions/Utility-v1.xml
new file mode 100644
index 0000000..e8eff02
--- /dev/null
+++ b/beatrix/src/test/resources/catalogs/testInArrearWithCatalogVersions/Utility-v1.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2014 The Billing Project, Inc.
+ ~
+ ~ Ning 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.
+ -->
+
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2016-02-08T00:00:00+00:00</effectiveDate>
+ <catalogName>TestInArrearWithCatalogVersions</catalogName>
+
+ <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+ <currencies>
+ <currency>USD</currency>
+ </currencies>
+
+ <units>
+ <unit name="kilowatt-hour"/>
+ </units>
+
+ <products>
+ <product name="Electricity">
+ <category>BASE</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ </rules>
+
+ <plans>
+
+ <plan name="electricity-monthly">
+ <product>Electricity</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <usages>
+ <usage name="electricity-monthly-usage" billingMode="IN_ARREAR" usageType="CONSUMABLE" tierBlockPolicy="ALL_TIERS">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <tiers>
+ <tier>
+ <blocks>
+ <tieredBlock>
+ <unit>kilowatt-hour</unit>
+ <size>1</size>
+ <prices>
+ <price>
+ <currency>USD</currency>
+ <value>1.50</value>
+ </price>
+ </prices>
+ <max>1000</max>
+ </tieredBlock>
+ <tieredBlock>
+ <unit>kilowatt-hour</unit>
+ <size>1</size>
+ <prices>
+ <price>
+ <currency>USD</currency>
+ <value>2.00</value>
+ </price>
+ </prices>
+ <max>-1</max>
+ </tieredBlock>
+ </blocks>
+ </tier>
+ </tiers>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>electricity-monthly</plan>
+ </plans>
+ </defaultPriceList>
+ </priceLists>
+</catalog>
diff --git a/beatrix/src/test/resources/catalogs/testInArrearWithCatalogVersions/Utility-v2.xml b/beatrix/src/test/resources/catalogs/testInArrearWithCatalogVersions/Utility-v2.xml
new file mode 100644
index 0000000..43c7c9f
--- /dev/null
+++ b/beatrix/src/test/resources/catalogs/testInArrearWithCatalogVersions/Utility-v2.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ 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.
+ -->
+
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2016-05-08T00:00:00+00:00</effectiveDate>
+ <catalogName>TestInArrearWithCatalogVersions</catalogName>
+
+ <recurringBillingMode>IN_ADVANCE</recurringBillingMode>
+
+ <currencies>
+ <currency>USD</currency>
+ </currencies>
+
+ <units>
+ <unit name="kilowatt-hour"/>
+ </units>
+
+ <products>
+ <product name="Electricity">
+ <category>BASE</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ </rules>
+
+ <plans>
+
+ <!-- Price increase from v1 version -->
+ <plan name="electricity-monthly">
+ <effectiveDateForExistingSubscriptions>2016-05-08T00:00:00+00:00</effectiveDateForExistingSubscriptions>
+ <product>Electricity</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <usages>
+ <usage name="electricity-monthly-usage" billingMode="IN_ARREAR" usageType="CONSUMABLE" tierBlockPolicy="ALL_TIERS">
+ <billingPeriod>MONTHLY</billingPeriod>
+ <tiers>
+ <tier>
+ <blocks>
+ <tieredBlock>
+ <unit>kilowatt-hour</unit>
+ <size>1</size>
+ <prices>
+ <price>
+ <currency>USD</currency>
+ <value>2.50</value>
+ </price>
+ </prices>
+ <max>1000</max>
+ </tieredBlock>
+ <tieredBlock>
+ <unit>kilowatt-hour</unit>
+ <size>1</size>
+ <prices>
+ <price>
+ <currency>USD</currency>
+ <value>3.00</value>
+ </price>
+ </prices>
+ <max>-1</max>
+ </tieredBlock>
+ </blocks>
+ </tier>
+ </tiers>
+ </usage>
+ </usages>
+ </finalPhase>
+ </plan>
+
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>electricity-monthly</plan>
+ </plans>
+ </defaultPriceList>
+ </priceLists>
+</catalog>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index a61fa3c..f47f3a0 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -97,6 +97,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
private String priceListName;
+ private DateTime catalogEffectiveDate;
+
// For deserialization
public DefaultPlan() {
initialPhases = new DefaultPlanPhase[0];
@@ -219,6 +221,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
this.priceListName = this.priceListName != null ? this.priceListName : findPriceListForPlan(catalog);
+ this.catalogEffectiveDate = new DateTime(catalog.getEffectiveDate());
}
@Override
@@ -402,4 +405,8 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
public void writeExternal(final ObjectOutput oo) throws IOException {
MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
}
+
+ public DateTime getCatalogEffectiveDate() {
+ return catalogEffectiveDate;
+ }
}
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 747a501..b35f865 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
@@ -212,11 +212,11 @@ public class ContiguousIntervalConsumableUsageInArrear extends ContiguousInterva
if (hasPreviousUsage) {
final Integer previousUsageQuantity = tierNum <= lastPreviousUsageTier ? previousUsage.get(tierNum - 1).getQuantity() : 0;
if (tierNum < lastPreviousUsageTier) {
- Preconditions.checkState(nbUsedTierBlocks == previousUsageQuantity, "Expected usage for tier='%d', unit='%s' to be full, instead found units='[%d/%d]'",
- tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity);
+ Preconditions.checkState(nbUsedTierBlocks == previousUsageQuantity, String.format("Expected usage for tier='%d', unit='%s' to be full, instead found units='[%d/%d]'",
+ tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
} else {
- Preconditions.checkState(nbUsedTierBlocks - previousUsageQuantity >= 0, "Expected usage for tier='%d', unit='%s' to contain at least as mush as current usage, instead found units='[%d/%d]",
- tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity);
+ Preconditions.checkState(nbUsedTierBlocks - previousUsageQuantity >= 0, String.format("Expected usage for tier='%d', unit='%s' to contain at least as mush as current usage, instead found units='[%d/%d]",
+ tierNum, tieredBlock.getUnit().getName(), nbUsedTierBlocks, previousUsageQuantity));
}
nbUsedTierBlocks = nbUsedTierBlocks - previousUsageQuantity;
}
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 93bbd42..bb3d830 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
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.BillingMode;
@@ -132,52 +133,56 @@ public class SubscriptionUsageInArrear {
List<ContiguousIntervalUsageInArrear> computeInArrearUsageInterval() {
final List<ContiguousIntervalUsageInArrear> usageIntervals = Lists.newLinkedList();
- final Map<String, ContiguousIntervalUsageInArrear> inFlightInArrearUsageIntervals = new HashMap<String, ContiguousIntervalUsageInArrear>();
+ final Map<UsageKey, ContiguousIntervalUsageInArrear> inFlightInArrearUsageIntervals = new HashMap<UsageKey, ContiguousIntervalUsageInArrear>();
- final Set<String> allSeenUsage = new HashSet<String>();
+ final Set<UsageKey> allSeenUsage = new HashSet<UsageKey>();
for (final BillingEvent event : subscriptionBillingEvents) {
+
+
// Extract all in arrear /consumable usage section for that billing event.
final List<Usage> usages = findUsageInArrearUsages(event);
- allSeenUsage.addAll(Collections2.transform(usages, new Function<Usage, String>() {
+ allSeenUsage.addAll(Collections2.transform(usages, new Function<Usage, UsageKey>() {
@Override
- public String apply(final Usage input) {
- return input.getName();
+ public UsageKey apply(final Usage input) {
+ return new UsageKey(input.getName(), event.getCatalogEffectiveDate());
}
}));
// All inflight usage interval are candidates to be closed unless we see that current billing event referencing the same usage section.
- final Set<String> toBeClosed = new HashSet<String>(allSeenUsage);
+ final Set<UsageKey> toBeClosed = new HashSet<UsageKey>(allSeenUsage);
for (final Usage usage : usages) {
+ final UsageKey usageKey = new UsageKey(usage.getName(), event.getCatalogEffectiveDate());
+
// Add inflight usage interval if non existent
- ContiguousIntervalUsageInArrear existingInterval = inFlightInArrearUsageIntervals.get(usage.getName());
+ 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);
- inFlightInArrearUsageIntervals.put(usage.getName(), existingInterval);
+ inFlightInArrearUsageIntervals.put(usageKey, existingInterval);
}
// Add billing event for that usage interval
existingInterval.addBillingEvent(event);
// Remove usage interval for toBeClosed set
- toBeClosed.remove(usage.getName());
+ toBeClosed.remove(usageKey);
}
// Build the usage interval that are no longer referenced
- for (final String usageName : toBeClosed) {
- final ContiguousIntervalUsageInArrear interval = inFlightInArrearUsageIntervals.remove(usageName);
+ for (final UsageKey usageKey : toBeClosed) {
+ final ContiguousIntervalUsageInArrear interval = inFlightInArrearUsageIntervals.remove(usageKey);
if (interval != null) {
interval.addBillingEvent(event);
usageIntervals.add(interval.build(true));
}
}
}
- for (final String usageName : inFlightInArrearUsageIntervals.keySet()) {
- usageIntervals.add(inFlightInArrearUsageIntervals.get(usageName).build(false));
+ for (final UsageKey usageKey : inFlightInArrearUsageIntervals.keySet()) {
+ usageIntervals.add(inFlightInArrearUsageIntervals.get(usageKey).build(false));
}
inFlightInArrearUsageIntervals.clear();
return usageIntervals;
@@ -233,4 +238,40 @@ public class SubscriptionUsageInArrear {
return perUsageNotificationDates != null ? perUsageNotificationDates : ImmutableMap.<String, LocalDate>of();
}
}
+
+ private static class UsageKey {
+
+ private final String usageName;
+ private final DateTime catalogVersion;
+
+ public UsageKey(final String usageName, final DateTime catalogVersion) {
+ this.usageName = usageName;
+ this.catalogVersion = catalogVersion;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final UsageKey usageKey = (UsageKey) o;
+
+ if (usageName != null ? !usageName.equals(usageKey.usageName) : usageKey.usageName != null) {
+ return false;
+ }
+ return catalogVersion != null ? catalogVersion.compareTo(usageKey.catalogVersion) == 0 : usageKey.catalogVersion == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = usageName != null ? usageName.hashCode() : 0;
+ result = 31 * result + (catalogVersion != null ? catalogVersion.hashCode() : 0);
+ return result;
+ }
+ }
+
}
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 73ae1fd..c19fdce 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -425,6 +425,11 @@ public class TestInvoiceHelper {
}
@Override
+ public DateTime getCatalogEffectiveDate() {
+ return null;
+ }
+
+ @Override
public int compareTo(final BillingEvent e1) {
if (!getSubscription().getId().equals(e1.getSubscription().getId())) { // First order by subscription
return getSubscription().getId().compareTo(e1.getSubscription().getId());
junction/pom.xml 3(+1 -2)
diff --git a/junction/pom.xml b/junction/pom.xml
index 4cef49f..bfc8c82 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -91,12 +91,11 @@
<dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-catalog</artifactId>
- <type>test-jar</type>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
index c08a7be..8af71c9 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEvent.java
@@ -24,6 +24,7 @@ import java.util.List;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
+import org.killbill.billing.catalog.DefaultPlan;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -59,6 +60,8 @@ public class DefaultBillingEvent implements BillingEvent {
private final boolean isDisableEvent;
private final PlanPhase nextPlanPhase;
+ private final DateTime catalogEffectiveDate;
+
public DefaultBillingEvent(final SubscriptionInternalEvent transition,
final SubscriptionBase subscription,
final int billCycleDayLocal,
@@ -88,6 +91,8 @@ public class DefaultBillingEvent implements BillingEvent {
final PlanPhase prevPlanPhase = (prevPhaseName != null && prevPlan != null) ? prevPlan.findPhase(prevPhaseName) : null;
this.billingPeriod = getRecurringBillingPeriod(prevPlanPhase);
}
+ // TODO It would be nice to have an Plan api to get that date; actually the following would be nice
+ this.catalogEffectiveDate = ((DefaultPlan) plan).getCatalogEffectiveDate();
this.billCycleDayLocal = billCycleDayLocal;
this.catalog = catalog;
@@ -124,6 +129,8 @@ public class DefaultBillingEvent implements BillingEvent {
this.usages = initializeUsage(isActive);
this.isDisableEvent = isDisableEvent;
this.nextPlanPhase = isDisableEvent ? null : planPhase;
+ this.catalogEffectiveDate = ((DefaultPlan) plan).getCatalogEffectiveDate();
+
}
@Override
@@ -348,4 +355,9 @@ public class DefaultBillingEvent implements BillingEvent {
}
return result;
}
+
+ @Override
+ public DateTime getCatalogEffectiveDate() {
+ return catalogEffectiveDate;
+ }
}