killbill-uncached

Merge remote-tracking branch 'killbill/work-for-release-0.19.x'

6/22/2018 1:29:29 PM

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);
+
+    }
 }