killbill-uncached
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestInArrearWithCatalogVersions.java 66(+53 -13)
subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java 4(+3 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java 4(+2 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java 33(+29 -4)
subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java 22(+9 -13)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 2(+1 -1)
Details
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 7e49d27..52efaf9 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
@@ -740,4 +740,57 @@ public class TestWithBCDUpdate extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 5, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 15), new LocalDate(2016, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
}
+
+
+
+ @Test(groups = "slow")
+ public void testWithBCDOnOperations() throws Exception {
+
+ final DateTime initialDate = new DateTime(2018, 6, 21, 0, 13, 42, 0, testTimeZone);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(21));
+ assertNotNull(account);
+
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("pistol-monthly-notrial");
+
+
+ busHandler.pushExpectedEvents( NextEvent.CREATE, NextEvent.BLOCK, NextEvent.BCD_CHANGE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+
+ // We will realign the BCD on the 15 as we create the subscription - ignoring the account setting on 21.
+ final UUID entitlementId = entitlementApi.createBaseEntitlement(account.getId(), new DefaultEntitlementSpecifier(spec, 15, null), null, null, null, false, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 1, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2018, 6, 21), new LocalDate(2018, 7, 15), InvoiceItemType.RECURRING, new BigDecimal("15.96")));
+
+
+ // Verify next month
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDays(24); // 2018-7-15
+ assertListenerStatus();
+
+ invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2018, 7, 15), new LocalDate(2018, 8, 15), InvoiceItemType.RECURRING, new BigDecimal("19.95")));
+
+
+ final Entitlement entitlement = entitlementApi.getEntitlementForId(entitlementId, callContext);
+
+ final PlanPhaseSpecifier spec2 = new PlanPhaseSpecifier("blowdart-monthly-notrial");
+
+ // Change plan EOT
+ // We will now realign the BCD on the 21 as we change the plan for the subscription.
+ entitlement.changePlan(new DefaultEntitlementSpecifier(spec2, 21, null), ImmutableList.<PluginProperty>of(), callContext);
+
+ busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.BCD_CHANGE, NextEvent.NULL_INVOICE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addMonths(1);
+ assertListenerStatus();
+
+
+ invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+ new ExpectedInvoiceItemCheck(new LocalDate(2018, 8, 15), new LocalDate(2018, 8, 21), InvoiceItemType.RECURRING, new BigDecimal("5.80")));
+
+
+ }
}
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 894f939..42cd3dd 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
@@ -85,10 +85,8 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 9), InvoiceItemType.USAGE, new BigDecimal("150.00")));
-
recordUsageData(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();
@@ -98,7 +96,6 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
}
-
@Test(groups = "slow")
public void testWithChangeWithinCatalog() throws Exception {
// 30 days month
@@ -139,10 +136,8 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 5, 9), InvoiceItemType.USAGE, new BigDecimal("150.00")));
-
recordUsageData(entitlementId, "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();
@@ -152,9 +147,6 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
}
-
-
-
// We are not using catalog versions in this test but testing the overridden value of 'readMaxRawUsagePreviousPeriod = 0'
@Test(groups = "slow")
public void testWithRemovedData() throws Exception {
@@ -180,8 +172,6 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 1, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("150.00")));
-
-
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.
@@ -193,8 +183,6 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
invoiceChecker.checkInvoice(account.getId(), 2, callContext,
new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 1), new LocalDate(2016, 6, 1), InvoiceItemType.USAGE, new BigDecimal("1900.00")));
-
-
// 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));
@@ -212,7 +200,6 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
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"))));
-
// 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
removeUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 6, 13));
@@ -227,5 +214,58 @@ public class TestInArrearWithCatalogVersions extends TestIntegrationBase {
new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 1), new LocalDate(2016, 8, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
}
+ @Test(groups = "slow")
+ public void testWithSubscriptionBCD() 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();
+
+ recordUsageData(entitlementId, "kilowatt-hour", new LocalDate(2016, 4, 5), 1L, callContext);
+ recordUsageData(entitlementId, "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,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 4, 1), new LocalDate(2016, 5, 1), InvoiceItemType.USAGE, new BigDecimal("150.00")));
+
+ final Entitlement bp = entitlementApi.getEntitlementForId(entitlementId, callContext);
+
+ recordUsageData(entitlementId, "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);
+
+ busHandler.pushExpectedEvents(NextEvent.BCD_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")));
+
+ // Original notification before we change BCD
+ busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+ clock.addDays(23);
+ assertListenerStatus();
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
+ clock.addDays(8);
+ 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,
+ new ExpectedInvoiceItemCheck(new LocalDate(2016, 5, 9), new LocalDate(2016, 6, 9), InvoiceItemType.USAGE, new BigDecimal("25.00")));
+
+ }
}
\ No newline at end of file
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index de6a600..d086be5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -201,9 +201,6 @@ public class InvoiceResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request) throws InvoiceApiException {
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
final Invoice invoice = invoiceApi.getInvoiceByNumber(invoiceNumber, tenantContext);
- if (invoice == null) {
- throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceNumber);
- }
final List<InvoiceItem> childInvoiceItems = withChildrenItems ? invoiceApi.getInvoiceItemsByParentInvoice(invoice.getId(), tenantContext) : null;
final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(invoice.getAccountId(), auditMode.getLevel(), tenantContext);
@@ -226,10 +223,6 @@ public class InvoiceResource extends JaxRsResourceBase {
@javax.ws.rs.core.Context final HttpServletRequest request) throws InvoiceApiException {
final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
final Invoice invoice = invoiceApi.getInvoiceByInvoiceItem(invoiceItemId, tenantContext);
- if (invoice == null) {
- throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
- }
-
final List<InvoiceItem> childInvoiceItems = withChildrenItems ? invoiceApi.getInvoiceItemsByParentInvoice(invoice.getId(), tenantContext) : null;
final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(invoice.getAccountId(), auditMode.getLevel(), tenantContext);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
index c7ef770..0222dbb 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionApiBase.java
@@ -151,7 +151,7 @@ public class SubscriptionApiBase {
final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, startEffectiveDate, addonUtils, context);
final UUID subscriptionId = UUIDs.randomUUID();
dryRunEvents = apiService.getEventsOnCreation(subscriptionId, startEffectiveDate, bundleStartDate, plan, inputSpec.getPhaseType(), plan.getPriceListName(),
- startEffectiveDate, catalog, context);
+ startEffectiveDate, entitlementSpecifier.getBillCycleDay(), catalog, context);
final SubscriptionBuilder builder = new SubscriptionBuilder()
.setId(subscriptionId)
.setBundleId(bundleId)
@@ -177,7 +177,7 @@ public class SubscriptionApiBase {
// We pass null for billingAlignment, accountTimezone, account BCD because this is not available which means that dryRun with START_OF_TERM BillingPolicy will fail
changeEffectiveDate = subscriptionForChange.getPlanChangeEffectiveDate(policy, null, -1, context);
}
- dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, true, catalog, context);
+ dryRunEvents = apiService.getEventsOnChangePlan(subscriptionForChange, plan, plan.getPriceListName(), changeEffectiveDate, true, entitlementSpecifier.getBillCycleDay(), catalog, context);
break;
case STOP_BILLING:
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
index 6bcfbc2..2b97be8 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseApiService.java
@@ -89,6 +89,7 @@ public interface SubscriptionBaseApiService {
public List<SubscriptionBaseEvent> getEventsOnCreation(UUID subscriptionId, DateTime alignStartDate, DateTime bundleStartDate,
Plan plan, PhaseType initialPhase,
String realPriceList, DateTime effectiveDate,
+ Integer bcd,
Catalog fullCatalog,
InternalTenantContext context)
throws CatalogApiException, SubscriptionBaseApiException;
@@ -96,7 +97,8 @@ public interface SubscriptionBaseApiService {
public List<SubscriptionBaseEvent> getEventsOnChangePlan(DefaultSubscriptionBase subscription, Plan newPlan,
String newPriceList, DateTime effectiveDate,
boolean addCancellationAddOnForEventsIfRequired,
- final Catalog fullCatalog,
+ Integer bcd,
+ Catalog fullCatalog,
InternalTenantContext context)
throws CatalogApiException, SubscriptionBaseApiException;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java
index 61c5429..46303de 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionBaseCreateApi.java
@@ -262,8 +262,8 @@ public class DefaultSubscriptionBaseCreateApi extends SubscriptionApiBase {
.setCategory(plan.getProduct().getCategory())
.setBundleStartDate(bundleStartDate)
.setAlignStartDate(effectiveDate)
- .setMigrated(isMigrated));
-
+ .setMigrated(isMigrated)
+ .setSubscriptionBCD(entitlement.getBillCycleDay()));
subscriptions.add(subscription);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index 31f9959..ac01a59 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -38,12 +38,10 @@ import org.killbill.billing.catalog.api.BillingActionPolicy;
import org.killbill.billing.catalog.api.BillingAlignment;
import org.killbill.billing.catalog.api.Catalog;
import org.killbill.billing.catalog.api.CatalogApiException;
-import org.killbill.billing.catalog.api.CatalogEntity;
import org.killbill.billing.catalog.api.CatalogInternalApi;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanChangeResult;
-import org.killbill.billing.catalog.api.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PlanSpecifier;
@@ -60,6 +58,8 @@ import org.killbill.billing.subscription.api.svcs.DefaultPlanPhasePriceOverrides
import org.killbill.billing.subscription.engine.addon.AddonUtils;
import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventBuilder;
+import org.killbill.billing.subscription.events.bcd.BCDEventData;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEventData;
import org.killbill.billing.subscription.events.user.ApiEvent;
@@ -152,6 +152,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
subscription.getInitialPhase(),
subscription.getRealPriceList(),
subscription.getEffectiveDate(),
+ subscription.getBuilder().getSubscriptionBCD(),
fullCatalog,
internalCallContext);
eventsMap.put(subscriptionBase.getId(), events);
@@ -429,7 +430,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final List<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
final List<SubscriptionBaseEvent> addOnCancelEvents = new ArrayList<SubscriptionBaseEvent>();
- final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPlan.getPriceListName(), effectiveDate, true, addOnSubscriptionsToBeCancelled, addOnCancelEvents, initialPhaseType, fullCatalog, internalCallContext);
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPlan.getPriceListName(), effectiveDate, true, addOnSubscriptionsToBeCancelled, addOnCancelEvents, initialPhaseType, spec.getBillCycleDay(), fullCatalog, internalCallContext);
dao.changePlan(subscription, changeEvents, addOnSubscriptionsToBeCancelled, addOnCancelEvents, fullCatalog, internalCallContext);
@@ -444,6 +445,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final PhaseType initialPhase,
final String realPriceList,
final DateTime effectiveDate,
+ final Integer bcd,
final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
final TimedPhase[] curAndNextPhases = planAligner.getCurrentAndNextTimedPhaseOnCreate(alignStartDate,
@@ -464,12 +466,23 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
.setFromDisk(true);
final SubscriptionBaseEvent creationEvent = new ApiEventCreate(createBuilder);
+
final TimedPhase nextTimedPhase = curAndNextPhases[1];
final PhaseEvent nextPhaseEvent = (nextTimedPhase != null) ?
PhaseEventData.createNextPhaseEvent(subscriptionId, nextTimedPhase.getPhase().getName(), nextTimedPhase.getStartPhase()) :
null;
final List<SubscriptionBaseEvent> events = new ArrayList<SubscriptionBaseEvent>();
events.add(creationEvent);
+
+ if (bcd != null) {
+ final SubscriptionBaseEvent bcdEvent = new BCDEventData(new BCDEventBuilder()
+ .setSubscriptionId(subscriptionId)
+ .setEffectiveDate(effectiveDate)
+ .setActive(true)
+ .setBillCycleDayLocal(bcd));
+ events.add(bcdEvent);
+ }
+
if (nextPhaseEvent != null) {
events.add(nextPhaseEvent);
}
@@ -480,12 +493,13 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
public List<SubscriptionBaseEvent> getEventsOnChangePlan(final DefaultSubscriptionBase subscription, final Plan newPlan,
final String newPriceList, final DateTime effectiveDate,
final boolean addCancellationAddOnForEventsIfRequired,
+ final Integer bcd,
final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled = new ArrayList<DefaultSubscriptionBase>();
final Collection<SubscriptionBaseEvent> addOnCancelEvents = new ArrayList<SubscriptionBaseEvent>();
- final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, addCancellationAddOnForEventsIfRequired, addOnSubscriptionsToBeCancelled, addOnCancelEvents, null, fullCatalog, internalTenantContext);
+ final List<SubscriptionBaseEvent> changeEvents = getEventsOnChangePlan(subscription, newPlan, newPriceList, effectiveDate, addCancellationAddOnForEventsIfRequired, addOnSubscriptionsToBeCancelled, addOnCancelEvents, null, bcd, fullCatalog, internalTenantContext);
changeEvents.addAll(addOnCancelEvents);
return changeEvents;
}
@@ -496,6 +510,7 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final Collection<DefaultSubscriptionBase> addOnSubscriptionsToBeCancelled,
final Collection<SubscriptionBaseEvent> addOnCancelEvents,
final PhaseType initialPhaseType,
+ final Integer bcd,
final Catalog fullCatalog,
final InternalTenantContext internalTenantContext) throws CatalogApiException, SubscriptionBaseApiException {
@@ -520,6 +535,16 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
final List<SubscriptionBaseEvent> changeEvents = new ArrayList<SubscriptionBaseEvent>();
// Only add the PHASE if it does not coincide with the CHANGE, if not this is 'just' a CHANGE.
changeEvents.add(changeEvent);
+
+ if (bcd != null) {
+ final SubscriptionBaseEvent bcdEvent = new BCDEventData(new BCDEventBuilder()
+ .setSubscriptionId(subscription.getId())
+ .setEffectiveDate(effectiveDate)
+ .setActive(true)
+ .setBillCycleDayLocal(bcd));
+ changeEvents.add(bcdEvent);
+ }
+
if (nextPhaseEvent != null && !nextPhaseEvent.getEffectiveDate().equals(changeEvent.getEffectiveDate())) {
changeEvents.add(nextPhaseEvent);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java
index 97df7fd..7440fb7 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBuilder.java
@@ -26,6 +26,7 @@ import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
public class SubscriptionBuilder {
+
private UUID id;
private UUID bundleId;
private String bundleExternalKey;
@@ -36,6 +37,7 @@ public class SubscriptionBuilder {
private ProductCategory category;
private DateTime chargedThroughDate;
private boolean migrated;
+ private Integer subscriptionBCD;
public SubscriptionBuilder() {
}
@@ -100,6 +102,11 @@ public class SubscriptionBuilder {
return this;
}
+ public SubscriptionBuilder setSubscriptionBCD(final Integer subscriptionBCD) {
+ this.subscriptionBCD = subscriptionBCD;
+ return this;
+ }
+
public UUID getId() {
return id;
}
@@ -139,19 +146,8 @@ public class SubscriptionBuilder {
return migrated;
}
- private void checkAllFieldsSet() {
- for (final Field cur : SubscriptionBuilder.class.getDeclaredFields()) {
- try {
- final Object value = cur.get(this);
- if (value == null) {
- throw new SubscriptionBaseError(String.format("Field %s has not been set for SubscriptionBase",
- cur.getName()));
- }
- } catch (IllegalAccessException e) {
- throw new SubscriptionBaseError(String.format("Failed to access value for field %s for SubscriptionBase",
- cur.getName()), e);
- }
- }
+ public Integer getSubscriptionBCD() {
+ return subscriptionBCD;
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 43a51bc..8a2ae63 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -719,7 +719,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
for (final SubscriptionBaseEvent cur : inputChangeEvents) {
createAndRefresh(transactional, new SubscriptionEventModelDao(cur), context);
- final boolean isBusEvent = cur.getEffectiveDate().compareTo(context.getCreatedDate()) <= 0 && (cur.getType() == EventType.API_USER);
+ final boolean isBusEvent = cur.getEffectiveDate().compareTo(context.getCreatedDate()) <= 0 && (cur.getType() == EventType.API_USER || cur.getType() == EventType.BCD_UPDATE);
recordBusOrFutureNotificationFromTransaction(subscription, cur, entitySqlDaoWrapperFactory, isBusEvent, 0, catalog, context);
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
index 1ca9893..0ee938d 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
@@ -28,7 +28,11 @@ import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.callcontext.CallContextBase;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.MutableCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.Duration;
import org.killbill.billing.catalog.api.PhaseType;
@@ -39,6 +43,7 @@ import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.invoice.api.DryRunArguments;
@@ -51,6 +56,7 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionEventModel
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.user.ApiEvent;
import org.killbill.billing.subscription.events.user.ApiEventType;
+import org.killbill.billing.util.callcontext.CallContext;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.tweak.HandleCallback;
@@ -683,4 +689,51 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
}
+
+ @Test(groups = "slow")
+ public void testChangePlanWithBCD() throws SubscriptionBillingApiException, SubscriptionBaseApiException {
+
+ final DateTime init = clock.getUTCNow();
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(bundle.getAccountId(),
+ ObjectType.ACCOUNT,
+ this.internalCallContext.getUpdatedBy(),
+ this.internalCallContext.getCallOrigin(),
+ this.internalCallContext.getContextUserType(),
+ this.internalCallContext.getUserToken(),
+ this.internalCallContext.getTenantRecordId());
+
+ DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, "Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
+ final PlanPhase trialPhase = subscription.getCurrentPhase();
+ assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
+
+ // MOVE TO NEXT PHASE
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertListenerStatus();
+
+ // SET CTD
+ final List<Duration> durationList = new ArrayList<Duration>();
+ durationList.add(trialPhase.getDuration());
+ //durationList.add(subscription.getCurrentPhase().getDuration());
+ final DateTime startDiscountPhase = TestSubscriptionHelper.addDuration(subscription.getStartDate(), durationList);
+ final Duration ctd = testUtil.getDurationMonth(1);
+ final DateTime newChargedThroughDate = TestSubscriptionHelper.addDuration(startDiscountPhase, ctd);
+ subscriptionInternalApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, internalCallContext);
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+
+ final PlanPhaseSpecifier planPhaseSpecifier = new PlanPhaseSpecifier("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount");
+
+ testListener.pushExpectedEvents(NextEvent.CHANGE, NextEvent.BCD_CHANGE);
+ subscription.changePlanWithDate(new DefaultEntitlementSpecifier(planPhaseSpecifier, 18, null), clock.getUTCNow(), callContext);
+ assertListenerStatus();
+
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ assertNotNull(subscription.getBillCycleDayLocal());
+ assertEquals(subscription.getBillCycleDayLocal().intValue(), 18);
+
+ }
+
+
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
index d659598..e112ad0 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCreate.java
@@ -26,21 +26,30 @@ import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.catalog.api.BillingPeriod;
import org.killbill.billing.catalog.api.PhaseType;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.entitlement.api.EntitlementSpecifier;
import org.killbill.billing.subscription.DefaultSubscriptionTestInitializer;
import org.killbill.billing.subscription.SubscriptionTestSuiteWithEmbeddedDB;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOns;
+import org.killbill.billing.subscription.api.SubscriptionBaseWithAddOnsSpecifier;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.google.common.collect.ImmutableList;
+
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertNotNull;
@@ -280,4 +289,41 @@ public class TestUserApiCreate extends SubscriptionTestSuiteWithEmbeddedDB {
subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
assertEquals(subscription.getState(), EntitlementState.ACTIVE);
}
+
+
+
+ @Test(groups = "slow")
+ public void testCreateSubscriptionWithBCD() throws SubscriptionBaseApiException {
+ final DateTime init = clock.getUTCNow();
+
+ final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(bundle.getAccountId(),
+ ObjectType.ACCOUNT,
+ this.internalCallContext.getUpdatedBy(),
+ this.internalCallContext.getCallOrigin(),
+ this.internalCallContext.getContextUserType(),
+ this.internalCallContext.getUserToken(),
+ this.internalCallContext.getTenantRecordId());
+
+ final Iterable<EntitlementSpecifier> specifiers = ImmutableList.<EntitlementSpecifier>of(new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("shotgun-monthly"), 18, null));
+ final SubscriptionBaseWithAddOnsSpecifier subscriptionBaseWithAddOnsSpecifier = new SubscriptionBaseWithAddOnsSpecifier(bundle.getId(),
+ bundle.getExternalKey(),
+ specifiers,
+ init.toLocalDate(),
+ false);
+
+ testListener.pushExpectedEvents(NextEvent.CREATE, NextEvent.BCD_CHANGE);
+ final List<SubscriptionBaseWithAddOns> subscriptionBaseWithAddOns = subscriptionInternalApi.createBaseSubscriptionsWithAddOns(ImmutableList.<SubscriptionBaseWithAddOnsSpecifier>of(subscriptionBaseWithAddOnsSpecifier),
+ false,
+ internalCallContext);
+ testListener.assertListenerStatus();
+
+ assertEquals(subscriptionBaseWithAddOns.size(), 1);
+ assertEquals(subscriptionBaseWithAddOns.get(0).getSubscriptionBaseList().size(), 1);
+
+ final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) subscriptionBaseWithAddOns.get(0).getSubscriptionBaseList().get(0);
+ assertNotNull(subscription);
+ assertNotNull(subscription.getBillCycleDayLocal());
+ assertEquals(subscription.getBillCycleDayLocal().intValue(), 18);
+
+ }
}