killbill-memoizeit
Changes
api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java 4(+4 -0)
api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java 6(+6 -0)
entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java 2(+2 -0)
junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java 11(+10 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java 55(+53 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java 13(+13 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java 4(+3 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java 6(+4 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 16(+14 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java 29(+29 -0)
subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java 29(+28 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java 3(+3 -0)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 42(+39 -3)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java 71(+46 -25)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java 2(+2 -0)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java 9(+5 -4)
subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java 55(+55 -0)
subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java 67(+67 -0)
subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java 3(+2 -1)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg 3(+3 -0)
subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java 15(+15 -0)
Details
diff --git a/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java b/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
index ea74974..1073d87 100644
--- a/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/SubscriptionInternalEvent.java
@@ -46,6 +46,8 @@ public interface SubscriptionInternalEvent extends BusInternalEvent {
String getPreviousPhase();
+ Integer getPreviousBillCycleDayLocal();
+
String getNextPlan();
String getNextPhase();
@@ -54,6 +56,8 @@ public interface SubscriptionInternalEvent extends BusInternalEvent {
String getNextPriceList();
+ Integer getNextBillCycleDayLocal();
+
Integer getRemainingEventsForUserOperation();
Long getTotalOrdering();
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
index df97745..e22fa16 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseInternalApi.java
@@ -96,4 +96,7 @@ public interface SubscriptionBaseInternalApi {
public Iterable<DateTime> getFutureNotificationsForAccount(InternalCallContext context);
public Map<UUID, DateTime> getNextFutureEventForSubscriptions(final SubscriptionBaseTransitionType eventType, final InternalCallContext internalCallContext);
+
+
+ public void updateBCD(final UUID subscriptionId, final int bcd, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException;
}
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
index 11318ce..5a7b24d 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseTransitionType.java
@@ -57,6 +57,10 @@ public enum SubscriptionBaseTransitionType {
*/
PHASE,
/**
+ * Update BCD for a specific subscription
+ */
+ BCD_CHANGE,
+ /**
* Generated by the system to mark the start of blocked billing overdue state. This is not on disk but computed by junction to create the billing events.
*/
START_BILLING_DISABLED,
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java
index a0a981f..0aba5d2 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/timeline/SubscriptionBaseTimeline.java
@@ -83,5 +83,11 @@ public interface SubscriptionBaseTimeline extends Entity {
* @return the name of the phase
*/
public String getPlanPhaseName();
+
+ /**
+ *
+ * @return the new billCycleDayLocal
+ */
+ public Integer getBillCycleDayLocal();
}
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java b/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java
index 77ab028..1703097 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/extbus/TestEventJson.java
@@ -27,7 +27,7 @@ import org.killbill.billing.ObjectType;
import org.killbill.billing.beatrix.BeatrixTestSuiteNoDB;
import org.killbill.billing.util.jackson.ObjectMapper;
-public class TestEventJson extends BeatrixTestSuiteNoDB {
+public class TestEventJson extends BeatrixTestSuiteNoDB {
private final ObjectMapper mapper = new ObjectMapper();
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 453884c..48fe9c4 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
@@ -200,6 +200,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
@Inject
protected SubscriptionApi subscriptionApi;
+
@Named(BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME)
@Inject
protected MockPaymentProviderPlugin paymentPlugin;
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
new file mode 100644
index 0000000..0fed776
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithBCDUpdate.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertNotNull;
+
+public class TestWithBCDUpdate extends TestIntegrationBase {
+
+ @Inject
+ protected SubscriptionBaseInternalApi subscriptionBaseInternalApi;
+
+ @Test(groups = "slow")
+ public void testBCDChangeForSubscriptionBillingAlignment() throws Exception {
+
+ final DateTime initialDate = new DateTime(2016, 4, 1, 0, 13, 42, 0, testTimeZone);
+ clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
+ assertNotNull(account);
+
+ // BP creation : Will set Account BCD to the first (2016-4-1 + 30 days = 2016-5-1)
+ final String productName = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+
+ // 2016-4-4 : (BP still in TRIAL)
+ // Laser-Scope is SUBSCRIPTION aligned with a 1 month DISCOUNT
+ clock.addDays(3);
+ final DefaultEntitlement aoEntitlement = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Laser-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY,
+ NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+ // 2016-5-1 : BP out of TRIAL + AO
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(27);
+ assertListenerStatus();
+
+ // 2016-5-4: Laser-Scope out of DISCOUNT
+ busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(3);
+ assertListenerStatus();
+
+ // 2016-6-1 : BP + AO invoice
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(28);
+ assertListenerStatus();
+
+ // 2016-6-4 : Change BCD for AO and
+ clock.addDays(3);
+
+ busHandler.pushExpectedEvents(NextEvent.BCD_CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ subscriptionBaseInternalApi.updateBCD(aoEntitlement.getId(), 4, internalCallContext);
+ assertListenerStatus();
+
+ final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+ List<Invoice> invoices = null;
+
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 4), new LocalDate(2016, 7, 4), InvoiceItemType.RECURRING, new BigDecimal("1999.95")));
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 6, 4), new LocalDate(2016, 7, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1799.96")));
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+ invoiceChecker.checkInvoice(invoices.get(5).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // 2016-7-1 : BP only
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(27);
+ assertListenerStatus();
+
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 1), new LocalDate(2016, 8, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+ invoiceChecker.checkInvoice(invoices.get(6).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ // 2016-7-4 : AO only
+ busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ clock.addDays(3);
+ assertListenerStatus();
+
+ expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 7, 4), new LocalDate(2016, 8, 4), InvoiceItemType.RECURRING, new BigDecimal("1999.95")));
+ invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+ invoiceChecker.checkInvoice(invoices.get(7).getId(), callContext, expectedInvoices);
+ expectedInvoices.clear();
+
+ checkNoMoreInvoiceToGenerate(account);
+ }
+
+}
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index 5208a5b..61e36c2 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -1404,9 +1404,11 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
null,
null,
null,
+ null,
nextPlan,
nextPhase,
nextPriceList,
+ null,
1L,
createdDate,
UUID.randomUUID(),
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index f263960..9da32aa 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -195,9 +195,18 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
}
+ Integer overridenBCD = null;
for (final EffectiveSubscriptionInternalEvent transition : billingTransitions) {
try {
- final int bcdLocal = bcdCalculator.calculateBcd(account, currentAccountBCD, bundleId, subscription, transition, context);
+ //
+ // A BCD_CHANGE transition defines a new billCycleDayLocal for the subscription and this overrides whatever computation
+ // occurs below (which is based on billing alignment policy). Also multiple of those BCD_CHANGE transitions could occur,
+ // to define different intervals with different billing cycle days.
+ //
+ overridenBCD = transition.getNextBillCycleDayLocal() != null ? transition.getNextBillCycleDayLocal() : overridenBCD;
+ final int bcdLocal = overridenBCD != null ?
+ overridenBCD :
+ bcdCalculator.calculateBcd(account, currentAccountBCD, bundleId, subscription, transition, context);
if (currentAccountBCD == 0 && !updatedAccountBCD) {
accountApi.updateBCD(account.getExternalKey(), bcdLocal, context);
diff --git a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
index 5b4748b..848e517 100644
--- a/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
+++ b/junction/src/test/java/org/killbill/billing/junction/plumbing/billing/TestBillingApi.java
@@ -281,9 +281,9 @@ public class TestBillingApi extends JunctionTestSuiteNoDB {
final PriceList nextPriceList = catalog.findPriceList(PriceListSet.DEFAULT_PRICELIST_NAME, now);
final EffectiveSubscriptionInternalEvent t = new MockEffectiveSubscriptionEvent(
- eventId, subId, bunId, then, now, null, null, null, null, EntitlementState.ACTIVE,
+ eventId, subId, bunId, then, now, null, null, null, null, null, EntitlementState.ACTIVE,
nextPlan.getName(), nextPhase.getName(),
- nextPriceList.getName(), 1L,
+ nextPriceList.getName(), null, 1L,
SubscriptionBaseTransitionType.CREATE, 1, null, 1L, 2L, null);
effectiveSubscriptionTransitions.add(t);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index 278959e..e93fcff 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -28,6 +28,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
@@ -70,7 +71,8 @@ import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseServ
import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
import org.killbill.billing.subscription.engine.dao.model.SubscriptionBundleModelDao;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
-import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventData;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.callcontext.CallContext;
@@ -601,7 +603,15 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
final Iterable<SubscriptionBaseEvent> filteredEvents = Iterables.filter(events, new Predicate<SubscriptionBaseEvent>() {
@Override
public boolean apply(final SubscriptionBaseEvent input) {
- return (eventType == SubscriptionBaseTransitionType.PHASE && input.getType() == EventType.PHASE) || input.getType() != EventType.PHASE;
+ switch (input.getType()) {
+ case PHASE:
+ return eventType == SubscriptionBaseTransitionType.PHASE;
+ case BCD_UPDATE:
+ return eventType == SubscriptionBaseTransitionType.BCD_CHANGE;
+ case API_USER:
+ default:
+ return true;
+ }
}
});
final Map<UUID, DateTime> result = filteredEvents.iterator().hasNext() ? new HashMap<UUID, DateTime>() : ImmutableMap.<UUID, DateTime>of();
@@ -614,6 +624,47 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
return result;
}
+ @Override
+ public void updateBCD(final UUID subscriptionId, final int bcd, final InternalCallContext internalCallContext) throws SubscriptionBaseApiException {
+ final DefaultSubscriptionBase subscription = (DefaultSubscriptionBase) getSubscriptionFromId(subscriptionId, internalCallContext);
+ final DateTime effectiveDate = getEffectiveDateForNewBCD(bcd, subscription, internalCallContext);
+ final BCDEvent bcdEvent = BCDEventData.createBCDEvent(subscription, effectiveDate, bcd);
+ dao.createBCDChangeEvent(subscription, bcdEvent, internalCallContext);
+ }
+
+
+ private DateTime getEffectiveDateForNewBCD(final int bcd, final DefaultSubscriptionBase subscription, final InternalCallContext internalCallContext) {
+ if (internalCallContext.getAccountRecordId() == null) {
+ throw new IllegalStateException("Need to have a valid context with accountRecordId");
+ }
+
+ // Today as seen by this account
+ final LocalDate startDate = internalCallContext.toLocalDate(clock.getUTCNow(), subscription.getStartDate());
+
+ // We want to compute a LocalDate in account TZ which maps to the provided 'bcd' and then compute an effectiveDate for when that BCD_CHANGE event needs to be triggered
+ //
+ // There is a bit of complexity to make sure the date we chose exists (e.g: a BCD of 31 in a february month would not make sense).
+ final int currentDay = startDate.getDayOfMonth();
+ final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue();
+
+ final LocalDate requestedDate;
+ if (bcd < currentDay) {
+ final LocalDate startDatePlusOneMonth = startDate.plusMonths(1);
+ final int lastDayOfNextMonth = startDatePlusOneMonth.dayOfMonth().getMaximumValue();
+ final int originalBCDORLastDayOfMonth = bcd <= lastDayOfNextMonth ? bcd : lastDayOfNextMonth;
+ requestedDate = new LocalDate(startDatePlusOneMonth.getYear(), startDatePlusOneMonth.getMonthOfYear(), originalBCDORLastDayOfMonth);
+ } else if (bcd == currentDay) {
+ // will default to immediate event
+ requestedDate = null;
+ } else if (bcd <= lastDayOfMonth) {
+ requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), bcd);
+ } else /* bcd > lastDayOfMonth && bcd > currentDay */{
+ requestedDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), lastDayOfMonth);
+ }
+ return requestedDate == null ? clock.getUTCNow() : internalCallContext.toUTCDateTime(requestedDate, clock.getUTCNow());
+ }
+
+
private DateTime getBundleStartDateWithSanity(final UUID bundleId, @Nullable final DefaultSubscriptionBase baseSubscription, final Plan plan,
final DateTime effectiveDate, final InternalTenantContext context) throws SubscriptionBaseApiException, CatalogApiException {
switch (plan.getProduct().getCategory()) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java
index fc33b62..8be1e76 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/timeline/DefaultSubscriptionBaseTimeline.java
@@ -37,6 +37,7 @@ import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.user.ApiEvent;
import org.killbill.billing.subscription.events.user.ApiEventType;
@@ -91,6 +92,7 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
PhaseType phaseType = null;
String planName = null;
String planPhaseName = null;
+ Integer billCycleDayLocal = null;
ApiEventType apiType = null;
switch (cur.getType()) {
@@ -105,6 +107,11 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
priceListName = prevPriceListName;
break;
+ case BCD_UPDATE:
+ final BCDEvent bcdEvent = (BCDEvent) cur;
+ billCycleDayLocal = bcdEvent.getBillCycleDayLocal();
+ break;
+
case API_USER:
final ApiEvent userEV = (ApiEvent) cur;
apiType = userEV.getApiEventType();
@@ -122,6 +129,7 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
final String planNameWithClosure = planName;
final String planPhaseNameWithClosure = planPhaseName;
+ final Integer billCycleDayLocalWithClosure = billCycleDayLocal;
final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, category, billingPeriod, priceListName, phaseType);
result.add(new ExistingEvent() {
@Override
@@ -158,6 +166,11 @@ public class DefaultSubscriptionBaseTimeline implements SubscriptionBaseTimeline
public String getPlanPhaseName() {
return planPhaseNameWithClosure;
}
+
+ @Override
+ public Integer getBillCycleDayLocal() {
+ return billCycleDayLocalWithClosure;
+ }
});
prevPlanName = planName;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
index aca165c..4e4d7b7 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultEffectiveSubscriptionEvent.java
@@ -42,10 +42,12 @@ public class DefaultEffectiveSubscriptionEvent extends DefaultSubscriptionEvent
@JsonProperty("previousPlan") final String previousPlan,
@JsonProperty("previousPhase") final String previousPhase,
@JsonProperty("previousPriceList") final String previousPriceList,
+ @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
@JsonProperty("nextState") final EntitlementState nextState,
@JsonProperty("nextPlan") final String nextPlan,
@JsonProperty("nextPhase") final String nextPhase,
@JsonProperty("nextPriceList") final String nextPriceList,
+ @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
@JsonProperty("totalOrdering") final Long totalOrdering,
@JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
@JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -54,7 +56,7 @@ public class DefaultEffectiveSubscriptionEvent extends DefaultSubscriptionEvent
@JsonProperty("searchKey2") final Long searchKey2,
@JsonProperty("userToken") final UUID userToken) {
super(eventId, subscriptionId, bundleId, effectiveTransitionTime, effectiveTransitionTime, previousState, previousPlan,
- previousPhase, previousPriceList, nextState, nextPlan, nextPhase, nextPriceList, totalOrdering,
+ previousPhase, previousPriceList, previousBillCycleDayLocal, nextState, nextPlan, nextPhase, nextPriceList, nextBillCycleDayLocal, totalOrdering,
transitionType, remainingEventsForUserOperation, startDate, searchKey1, searchKey2, userToken);
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
index 2c78684..1e918ef 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultRequestedSubscriptionEvent.java
@@ -40,10 +40,12 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent
@JsonProperty("previousPlan") final String previousPlan,
@JsonProperty("previousPhase") final String previousPhase,
@JsonProperty("previousPriceList") final String previousPriceList,
+ @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
@JsonProperty("nextState") final EntitlementState nextState,
@JsonProperty("nextPlan") final String nextPlan,
@JsonProperty("nextPhase") final String nextPhase,
@JsonProperty("nextPriceList") final String nextPriceList,
+ @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
@JsonProperty("totalOrdering") final Long totalOrdering,
@JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
@JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -52,7 +54,7 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent
@JsonProperty("searchKey2") final Long searchKey2,
@JsonProperty("userToken") final UUID userToken) {
super(eventId, subscriptionId, bundleId, requestedTransitionTime, effectiveTransitionTime, previousState, previousPlan,
- previousPhase, previousPriceList, nextState, nextPlan, nextPhase, nextPriceList, totalOrdering,
+ previousPhase, previousPriceList, previousBillCycleDayLocal, nextState, nextPlan, nextPhase, nextPriceList, nextBillCycleDayLocal, totalOrdering,
transitionType, remainingEventsForUserOperation, startDate, searchKey1, searchKey2, userToken);
}
@@ -63,6 +65,6 @@ public class DefaultRequestedSubscriptionEvent extends DefaultSubscriptionEvent
final Long searchKey2,
final UUID userToken) {
this(nextEvent.getId(), nextEvent.getSubscriptionId(), subscription.getBundleId(), nextEvent.getEffectiveDate(), nextEvent.getEffectiveDate(),
- null, null, null, null, null, null, null, null, nextEvent.getTotalOrdering(), transitionType, 0, null, searchKey1, searchKey2, userToken);
+ null, null, null, null, null, null, null, null, null, null, nextEvent.getTotalOrdering(), transitionType, 0, null, searchKey1, searchKey2, userToken);
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
index 6f04266..af7bce3 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
@@ -50,6 +50,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData
import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionDataIterator.Visibility;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.user.ApiEvent;
import org.killbill.billing.subscription.events.user.ApiEventType;
@@ -556,6 +557,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
String nextPlanName = null;
String nextPhaseName = null;
String nextPriceListName = null;
+ Integer nextBillingCycleDayLocal = null;
UUID prevEventId = null;
DateTime prevCreatedDate = null;
@@ -563,6 +565,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
PriceList previousPriceList = null;
Plan previousPlan = null;
PlanPhase previousPhase = null;
+ Integer previousBillingCycleDayLocal = null;
transitions = new LinkedList<SubscriptionBaseTransition>();
@@ -586,6 +589,11 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
nextPhaseName = phaseEV.getPhase();
break;
+ case BCD_UPDATE:
+ final BCDEvent bcdEvent = (BCDEvent) cur;
+ nextBillingCycleDayLocal = bcdEvent.getBillCycleDayLocal();
+ break;
+
case API_USER:
final ApiEvent userEV = (ApiEvent) cur;
apiEventType = userEV.getApiEventType();
@@ -641,15 +649,18 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
final DateTime catalogEffectiveDateForPriceList = transitions.isEmpty() ? cur.getEffectiveDate() : transitions.get(0).getEffectiveTransitionTime();
nextPriceList = (nextPriceListName != null) ? catalog.findPriceList(nextPriceListName, catalogEffectiveDateForPriceList) : null;
- final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData(
+ final SubscriptionBaseTransitionData transition = new SubscriptionBaseTransitionData (
cur.getId(), id, bundleId, cur.getType(), apiEventType,
cur.getEffectiveDate(),
prevEventId, prevCreatedDate,
previousState, previousPlan, previousPhase,
previousPriceList,
+ previousBillingCycleDayLocal,
nextEventId, nextCreatedDate,
nextState, nextPlan, nextPhase,
- nextPriceList, cur.getTotalOrdering(),
+ nextPriceList,
+ nextBillingCycleDayLocal,
+ cur.getTotalOrdering(),
cur.getCreatedDate(),
nextUserToken,
isFromDisk);
@@ -662,6 +673,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
previousPriceList = nextPriceList;
prevEventId = nextEventId;
prevCreatedDate = nextCreatedDate;
+ previousBillingCycleDayLocal = nextBillingCycleDayLocal;
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
index 04601ac..89f8aa7 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionEvent.java
@@ -41,14 +41,17 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
private final String previousPriceList;
private final String previousPlan;
private final String previousPhase;
+ private final Integer previousBillCycleDayLocal;
private final EntitlementState nextState;
private final String nextPriceList;
private final String nextPlan;
private final String nextPhase;
+ private final Integer nextBillCycleDayLocal;
private final Integer remainingEventsForUserOperation;
private final SubscriptionBaseTransitionType transitionType;
private final DateTime startDate;
+
public DefaultSubscriptionEvent(final SubscriptionBaseTransitionData in, final DateTime startDate,
final Long searchKey1,
final Long searchKey2,
@@ -62,10 +65,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
(in.getPreviousPlan() != null) ? in.getPreviousPlan().getName() : null,
(in.getPreviousPhase() != null) ? in.getPreviousPhase().getName() : null,
(in.getPreviousPriceList() != null) ? in.getPreviousPriceList().getName() : null,
+ in.getPreviousBillingCycleDayLocal(),
in.getNextState(),
(in.getNextPlan() != null) ? in.getNextPlan().getName() : null,
(in.getNextPhase() != null) ? in.getNextPhase().getName() : null,
(in.getNextPriceList() != null) ? in.getNextPriceList().getName() : null,
+ in.getNextBillingCycleDayLocal(),
in.getTotalOrdering(),
in.getTransitionType(),
in.getRemainingEventsForUserOperation(),
@@ -85,10 +90,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
@JsonProperty("previousPlan") final String previousPlan,
@JsonProperty("previousPhase") final String previousPhase,
@JsonProperty("previousPriceList") final String previousPriceList,
+ @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
@JsonProperty("nextState") final EntitlementState nextState,
@JsonProperty("nextPlan") final String nextPlan,
@JsonProperty("nextPhase") final String nextPhase,
@JsonProperty("nextPriceList") final String nextPriceList,
+ @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
@JsonProperty("totalOrdering") final Long totalOrdering,
@JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
@JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -104,11 +111,13 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
this.effectiveTransitionTime = effectiveTransitionTime;
this.previousState = previousState;
this.previousPriceList = previousPriceList;
+ this.previousBillCycleDayLocal = previousBillCycleDayLocal;
this.previousPlan = previousPlan;
this.previousPhase = previousPhase;
this.nextState = nextState;
this.nextPlan = nextPlan;
this.nextPriceList = nextPriceList;
+ this.nextBillCycleDayLocal = nextBillCycleDayLocal;
this.nextPhase = nextPhase;
this.totalOrdering = totalOrdering;
this.transitionType = transitionType;
@@ -154,6 +163,11 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
}
@Override
+ public Integer getPreviousBillCycleDayLocal() {
+ return previousBillCycleDayLocal;
+ }
+
+ @Override
public String getNextPlan() {
return nextPlan;
}
@@ -179,6 +193,11 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
}
@Override
+ public Integer getNextBillCycleDayLocal() {
+ return nextBillCycleDayLocal;
+ }
+
+ @Override
public Integer getRemainingEventsForUserOperation() {
return remainingEventsForUserOperation;
}
@@ -221,10 +240,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
sb.append(", effectiveTransitionTime=").append(effectiveTransitionTime);
sb.append(", previousState=").append(previousState);
sb.append(", previousPriceList='").append(previousPriceList).append('\'');
+ sb.append(", previousBillCycleDayLocal='").append(previousBillCycleDayLocal).append('\'');
sb.append(", previousPlan='").append(previousPlan).append('\'');
sb.append(", previousPhase='").append(previousPhase).append('\'');
sb.append(", nextState=").append(nextState);
sb.append(", nextPriceList='").append(nextPriceList).append('\'');
+ sb.append(", nextBillCycleDayLocal='").append(nextBillCycleDayLocal).append('\'');
sb.append(", nextPlan='").append(nextPlan).append('\'');
sb.append(", nextPhase='").append(nextPhase).append('\'');
sb.append(", remainingEventsForUserOperation=").append(remainingEventsForUserOperation);
@@ -263,6 +284,9 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
return false;
}
+ if (nextBillCycleDayLocal != null ? !nextBillCycleDayLocal.equals(that.nextBillCycleDayLocal) : that.nextBillCycleDayLocal != null) {
+ return false;
+ }
if (nextState != that.nextState) {
return false;
}
@@ -275,6 +299,9 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
if (previousPriceList != null ? !previousPriceList.equals(that.previousPriceList) : that.previousPriceList != null) {
return false;
}
+ if (previousBillCycleDayLocal != null ? !previousBillCycleDayLocal.equals(that.previousBillCycleDayLocal) : that.previousBillCycleDayLocal != null) {
+ return false;
+ }
if (previousState != that.previousState) {
return false;
}
@@ -309,10 +336,12 @@ public abstract class DefaultSubscriptionEvent extends BusEventBase implements S
result = 31 * result + (effectiveTransitionTime != null ? effectiveTransitionTime.hashCode() : 0);
result = 31 * result + (previousState != null ? previousState.hashCode() : 0);
result = 31 * result + (previousPriceList != null ? previousPriceList.hashCode() : 0);
+ result = 31 * result + (previousBillCycleDayLocal != null ? previousBillCycleDayLocal.hashCode() : 0);
result = 31 * result + (previousPlan != null ? previousPlan.hashCode() : 0);
result = 31 * result + (previousPhase != null ? previousPhase.hashCode() : 0);
result = 31 * result + (nextState != null ? nextState.hashCode() : 0);
result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+ result = 31 * result + (nextBillCycleDayLocal != null ? nextBillCycleDayLocal.hashCode() : 0);
result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
result = 31 * result + (nextPhase != null ? nextPhase.hashCode() : 0);
result = 31 * result + (remainingEventsForUserOperation != null ? remainingEventsForUserOperation.hashCode() : 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
index df2ccad..34de332 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionData.java
@@ -39,6 +39,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
private final DateTime effectiveTransitionTime;
private final EntitlementState previousState;
private final PriceList previousPriceList;
+ private final Integer previousBillingCycleDayLocal;
private final UUID previousEventId;
private final DateTime previousEventCreatedDate;
private final Plan previousPlan;
@@ -47,6 +48,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
private final DateTime nextEventCreatedDate;
private final EntitlementState nextState;
private final PriceList nextPriceList;
+ private final Integer nextBillingCycleDayLocal;
private final Plan nextPlan;
private final PlanPhase nextPhase;
private final Boolean isFromDisk;
@@ -66,12 +68,14 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
final Plan previousPlan,
final PlanPhase previousPhase,
final PriceList previousPriceList,
+ final Integer previousBillingCycleDayLocal,
final UUID nextEventId,
final DateTime nextEventCreatedDate,
final EntitlementState nextState,
final Plan nextPlan,
final PlanPhase nextPhase,
final PriceList nextPriceList,
+ final Integer nextBillingCycleDayLocal,
final Long totalOrdering,
final DateTime createdDate,
final UUID userToken,
@@ -84,11 +88,13 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
this.effectiveTransitionTime = effectiveTransitionTime;
this.previousState = previousState;
this.previousPriceList = previousPriceList;
+ this.previousBillingCycleDayLocal = previousBillingCycleDayLocal;
this.previousPlan = previousPlan;
this.previousPhase = previousPhase;
this.nextState = nextState;
this.nextPlan = nextPlan;
this.nextPriceList = nextPriceList;
+ this.nextBillingCycleDayLocal = nextBillingCycleDayLocal;
this.nextPhase = nextPhase;
this.totalOrdering = totalOrdering;
this.previousEventId = previousEventId;
@@ -118,6 +124,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
this.previousEventCreatedDate = input.getPreviousEventCreatedDate();
this.previousState = input.getPreviousState();
this.previousPriceList = input.getPreviousPriceList();
+ this.previousBillingCycleDayLocal = input.getPreviousBillingCycleDayLocal();
this.previousPlan = input.getPreviousPlan();
this.previousPhase = input.getPreviousPhase();
this.nextEventId = input.getNextEventId();
@@ -125,6 +132,7 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
this.nextState = input.getNextState();
this.nextPlan = input.getNextPlan();
this.nextPriceList = input.getNextPriceList();
+ this.nextBillingCycleDayLocal = input.getNextBillingCycleDayLocal();
this.nextPhase = input.getNextPhase();
this.totalOrdering = input.getTotalOrdering();
this.isFromDisk = input.isFromDisk();
@@ -208,6 +216,14 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
return nextPriceList;
}
+ public Integer getPreviousBillingCycleDayLocal() {
+ return previousBillingCycleDayLocal;
+ }
+
+ public Integer getNextBillingCycleDayLocal() {
+ return nextBillingCycleDayLocal;
+ }
+
public UUID getUserToken() {
return userToken;
}
@@ -227,6 +243,8 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
return apiEventType.getSubscriptionTransitionType();
case PHASE:
return SubscriptionBaseTransitionType.PHASE;
+ case BCD_UPDATE:
+ return SubscriptionBaseTransitionType.BCD_CHANGE;
default:
throw new SubscriptionBaseError("Unexpected event type " + eventType);
}
@@ -270,10 +288,12 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
sb.append(", effectiveTransitionTime=").append(effectiveTransitionTime);
sb.append(", previousState=").append(previousState);
sb.append(", previousPriceList=").append(previousPriceList);
+ sb.append(", previousBillingCycleDayLocal=").append(previousBillingCycleDayLocal);
sb.append(", previousPlan=").append(previousPlan);
sb.append(", previousPhase=").append(previousPhase);
sb.append(", nextState=").append(nextState);
sb.append(", nextPriceList=").append(nextPriceList);
+ sb.append(", nextBillingCycleDayLocal=").append(nextBillingCycleDayLocal);
sb.append(", nextPlan=").append(nextPlan);
sb.append(", nextPhase=").append(nextPhase);
sb.append(", isFromDisk=").append(isFromDisk);
@@ -321,6 +341,9 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
if (nextPriceList != null ? !nextPriceList.equals(that.nextPriceList) : that.nextPriceList != null) {
return false;
}
+ if (nextBillingCycleDayLocal != null ? !nextBillingCycleDayLocal.equals(that.nextBillingCycleDayLocal) : that.nextBillingCycleDayLocal != null) {
+ return false;
+ }
if (nextState != that.nextState) {
return false;
}
@@ -333,6 +356,9 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
if (previousPriceList != null ? !previousPriceList.equals(that.previousPriceList) : that.previousPriceList != null) {
return false;
}
+ if (previousBillingCycleDayLocal != null ? !previousBillingCycleDayLocal.equals(that.previousBillingCycleDayLocal) : that.previousBillingCycleDayLocal != null) {
+ return false;
+ }
if (previousState != that.previousState) {
return false;
}
@@ -348,7 +374,6 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
if (userToken != null ? !userToken.equals(that.userToken) : that.userToken != null) {
return false;
}
-
return true;
}
@@ -363,10 +388,12 @@ public class SubscriptionBaseTransitionData implements SubscriptionBaseTransitio
result = 31 * result + (effectiveTransitionTime != null ? effectiveTransitionTime.hashCode() : 0);
result = 31 * result + (previousState != null ? previousState.hashCode() : 0);
result = 31 * result + (previousPriceList != null ? previousPriceList.hashCode() : 0);
+ result = 31 * result + (previousBillingCycleDayLocal != null ? previousBillingCycleDayLocal.hashCode() : 0);
result = 31 * result + (previousPlan != null ? previousPlan.hashCode() : 0);
result = 31 * result + (previousPhase != null ? previousPhase.hashCode() : 0);
result = 31 * result + (nextState != null ? nextState.hashCode() : 0);
result = 31 * result + (nextPriceList != null ? nextPriceList.hashCode() : 0);
+ result = 31 * result + (nextBillingCycleDayLocal != null ? nextBillingCycleDayLocal.hashCode() : 0);
result = 31 * result + (nextPlan != null ? nextPlan.hashCode() : 0);
result = 31 * result + (nextPhase != null ? nextPhase.hashCode() : 0);
result = 31 * result + (isFromDisk != null ? isFromDisk.hashCode() : 0);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index e5e84ab..07fc47d 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -163,6 +163,9 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
} else if (event.getType() == EventType.API_USER && subscription.getCategory() == ProductCategory.BASE) {
final CallContext callContext = internalCallContextFactory.createCallContext(context);
eventSent = onBasePlanEvent(subscription, event, callContext);
+ } else if (event.getType() == EventType.BCD_UPDATE) {
+ // TODO STEPH do we want to populate more info in the event ?
+ eventSent = true;
}
if (!eventSent) {
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 119bfe7..7713f4d 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
@@ -67,6 +67,8 @@ import org.killbill.billing.subscription.engine.dao.model.SubscriptionModelDao;
import org.killbill.billing.subscription.events.EventBaseBuilder;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventBuilder;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEventBuilder;
import org.killbill.billing.subscription.events.user.ApiEvent;
@@ -696,6 +698,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
String curPlan = null;
String curPhase = null;
String curPriceList = null;
+
for (SubscriptionBaseEvent cur : changeEvents) {
switch (cur.getType()) {
case API_USER:
@@ -710,6 +713,9 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
curPhase = phaseEvent.getPhase();
break;
+ case BCD_UPDATE:
+ break;
+
default:
throw new SubscriptionBaseError("Unknown event type " + cur.getType());
}
@@ -983,9 +989,20 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
// Set total ordering value of the fake dryRun event to make sure billing events are correctly ordered
final SubscriptionBaseEvent curAdjustedDryRun;
if (!events.isEmpty()) {
- final EventBaseBuilder eventBuilder = (curDryRun.getType() == EventType.API_USER) ?
- new ApiEventBuilder((ApiEvent) curDryRun) :
- new PhaseEventBuilder((PhaseEvent) curDryRun);
+
+ final EventBaseBuilder eventBuilder;
+ switch(curDryRun.getType()) {
+ case PHASE:
+ eventBuilder = new PhaseEventBuilder((PhaseEvent) curDryRun);
+ break;
+ case BCD_UPDATE:
+ eventBuilder = new BCDEventBuilder((BCDEvent) curDryRun);
+ break;
+ case API_USER:
+ default:
+ eventBuilder = new ApiEventBuilder((ApiEvent) curDryRun);
+ break;
+ }
eventBuilder.setTotalOrdering(events.get(events.size() - 1).getTotalOrdering() + 1);
curAdjustedDryRun = eventBuilder.build();
@@ -1045,6 +1062,25 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
});
}
+ @Override
+ public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final InternalCallContext context) {
+ transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+ @Override
+ public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+ final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
+ transactional.create(new SubscriptionEventModelDao(bcdEvent), context);
+
+ // Notify the Bus
+ notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, bcdEvent, SubscriptionBaseTransitionType.BCD_CHANGE, context);
+ final boolean isBusEvent = bcdEvent.getEffectiveDate().compareTo(clock.getUTCNow()) <= 0;
+ recordBusOrFutureNotificationFromTransaction(subscription, bcdEvent, entitySqlDaoWrapperFactory, isBusEvent, 0, context);
+
+ return null;
+ }
+ });
+
+ }
+
private DefaultSubscriptionBase createSubscriptionForInternalUse(final SubscriptionBase shellSubscription, final List<SubscriptionBaseEvent> events, final InternalTenantContext context) throws CatalogApiException {
final DefaultSubscriptionBase result = new DefaultSubscriptionBase(new SubscriptionBuilder(((DefaultSubscriptionBase) shellSubscription)), null, clock);
if (events.size() > 0) {
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
index 73d4492..a3c9b45 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
@@ -19,24 +19,16 @@ package org.killbill.billing.subscription.engine.dao.model;
import java.util.UUID;
import org.joda.time.DateTime;
-
+import org.killbill.billing.subscription.events.EventBaseBuilder;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
import org.killbill.billing.subscription.events.SubscriptionBaseEvent.EventType;
-import org.killbill.billing.subscription.events.EventBaseBuilder;
+import org.killbill.billing.subscription.events.bcd.BCDEvent;
+import org.killbill.billing.subscription.events.bcd.BCDEventBuilder;
import org.killbill.billing.subscription.events.phase.PhaseEvent;
import org.killbill.billing.subscription.events.phase.PhaseEventBuilder;
-import org.killbill.billing.subscription.events.phase.PhaseEventData;
import org.killbill.billing.subscription.events.user.ApiEvent;
import org.killbill.billing.subscription.events.user.ApiEventBuilder;
-import org.killbill.billing.subscription.events.user.ApiEventCancel;
-import org.killbill.billing.subscription.events.user.ApiEventChange;
-import org.killbill.billing.subscription.events.user.ApiEventCreate;
-import org.killbill.billing.subscription.events.user.ApiEventMigrateBilling;
-import org.killbill.billing.subscription.events.user.ApiEventMigrateSubscription;
-import org.killbill.billing.subscription.events.user.ApiEventReCreate;
-import org.killbill.billing.subscription.events.user.ApiEventTransfer;
import org.killbill.billing.subscription.events.user.ApiEventType;
-import org.killbill.billing.subscription.events.user.ApiEventUncancel;
import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.dao.EntityModelDao;
@@ -54,6 +46,9 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
private String phaseName;
private String priceListName;
private long currentVersion;
+ private int billingCycleDayLocal;
+
+
private boolean isActive;
public SubscriptionEventModelDao() {
@@ -63,7 +58,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
public SubscriptionEventModelDao(final UUID id, final long totalOrdering, final EventType eventType, final ApiEventType userType,
final DateTime requestedDate, final DateTime effectiveDate, final UUID subscriptionId,
final String planName, final String phaseName, final String priceListName, final long currentVersion,
- final boolean active, final DateTime createDate, final DateTime updateDate) {
+ final int billingCycleDayLocal, final boolean active, final DateTime createDate, final DateTime updateDate) {
super(id, createDate, updateDate);
this.totalOrdering = totalOrdering;
this.eventType = eventType;
@@ -75,6 +70,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
this.phaseName = phaseName;
this.priceListName = priceListName;
this.currentVersion = currentVersion;
+ this.billingCycleDayLocal = billingCycleDayLocal;
this.isActive = active;
}
@@ -87,9 +83,16 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
this.effectiveDate = src.getEffectiveDate();
this.subscriptionId = src.getSubscriptionId();
this.planName = eventType == EventType.API_USER ? ((ApiEvent) src).getEventPlan() : null;
- this.phaseName = eventType == EventType.API_USER ? ((ApiEvent) src).getEventPlanPhase() : ((PhaseEvent) src).getPhase();
+ if (eventType == EventType.API_USER) {
+ this.phaseName = ((ApiEvent) src).getEventPlanPhase();
+ } else if (eventType == EventType.PHASE) {
+ this.phaseName = ((PhaseEvent) src).getPhase();
+ } else {
+ this.phaseName = null;
+ }
this.priceListName = eventType == EventType.API_USER ? ((ApiEvent) src).getPriceList() : null;
this.currentVersion = src.getActiveVersion();
+ this.billingCycleDayLocal = eventType == EventType.BCD_UPDATE ? ((BCDEvent) src).getBillCycleDayLocal() : 0;
this.isActive = src.isActive();
}
@@ -133,6 +136,10 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
return currentVersion;
}
+ public int getBillingCycleDayLocal() {
+ return billingCycleDayLocal;
+ }
+
// TODO required for jdbi binder
public boolean getIsActive() {
return isActive;
@@ -182,6 +189,10 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
this.currentVersion = currentVersion;
}
+ public void setBillingCycleDayLocal(final int billingCycleDayLocal) {
+ this.billingCycleDayLocal = billingCycleDayLocal;
+ }
+
public void setIsActive(final boolean isActive) {
this.isActive = isActive;
}
@@ -192,17 +203,22 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
return null;
}
- final EventBaseBuilder<?> base = ((src.getEventType() == EventType.PHASE) ?
- new PhaseEventBuilder() :
- new ApiEventBuilder())
- .setTotalOrdering(src.getTotalOrdering())
- .setUuid(src.getId())
- .setSubscriptionId(src.getSubscriptionId())
- .setCreatedDate(src.getCreatedDate())
- .setUpdatedDate(src.getUpdatedDate())
- .setEffectiveDate(src.getEffectiveDate())
- .setActiveVersion(src.getCurrentVersion())
- .setActive(src.isActive());
+ final EventBaseBuilder<?> base;
+ if (src.getEventType() == EventType.PHASE) {
+ base = new PhaseEventBuilder();
+ } else if (src.getEventType() == EventType.BCD_UPDATE) {
+ base = new BCDEventBuilder();
+ } else {
+ base = new ApiEventBuilder();
+ }
+ base.setTotalOrdering(src.getTotalOrdering())
+ .setUuid(src.getId())
+ .setSubscriptionId(src.getSubscriptionId())
+ .setCreatedDate(src.getCreatedDate())
+ .setUpdatedDate(src.getUpdatedDate())
+ .setEffectiveDate(src.getEffectiveDate())
+ .setActiveVersion(src.getCurrentVersion())
+ .setActive(src.isActive());
SubscriptionBaseEvent result;
if (src.getEventType() == EventType.PHASE) {
@@ -216,6 +232,8 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
.setApiEventType(src.getUserType())
.setFromDisk(true);
result = builder.build();
+ } else if (src.getEventType() == EventType.BCD_UPDATE) {
+ result = (new BCDEventBuilder(base).setBillCycleDayLocal(src.getBillingCycleDayLocal())).build();
} else {
throw new SubscriptionBaseError(String.format("Can't figure out event %s", src.getEventType()));
}
@@ -236,6 +254,7 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
sb.append(", phaseName='").append(phaseName).append('\'');
sb.append(", priceListName='").append(priceListName).append('\'');
sb.append(", currentVersion=").append(currentVersion);
+ sb.append(", billingCycleDayLocal=").append(billingCycleDayLocal);
sb.append(", isActive=").append(isActive);
sb.append('}');
return sb.toString();
@@ -288,7 +307,9 @@ public class SubscriptionEventModelDao extends EntityModelDaoBase implements Ent
if (userType != that.userType) {
return false;
}
-
+ if (billingCycleDayLocal != that.billingCycleDayLocal) {
+ return false;
+ }
return true;
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
index 487fae3..fbd13c2 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionDao.java
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.api.CatalogApiException;
@@ -100,5 +101,6 @@ public interface SubscriptionDao extends EntityDao<SubscriptionBundleModelDao, S
public void updateBundleExternalKey(UUID bundleId, String externalKey, InternalCallContext context);
+ public void createBCDChangeEvent(DefaultSubscriptionBase subscription, SubscriptionBaseEvent bcdEvent, InternalCallContext context);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index 177df8b..6f7af5c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -18,7 +18,10 @@ package org.killbill.billing.subscription.engine.dao;
import java.util.Date;
import java.util.List;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import org.killbill.billing.entity.EntityPersistenceException;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -54,15 +57,13 @@ public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventM
@SqlQuery
public List<SubscriptionEventModelDao> getFutureActiveEventForSubscription(@Bind("subscriptionId") String subscriptionId,
- @Bind("now") Date now,
- @BindBean final InternalTenantContext context);
+ @Bind("now") Date now,
+ @BindBean final InternalTenantContext context);
@SqlQuery
public List<SubscriptionEventModelDao> getEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
@BindBean final InternalTenantContext context);
-
@SqlQuery
public List<SubscriptionEventModelDao> getFutureActiveEventsForAccount(@Bind("now") Date now, @BindBean final InternalTenantContext context);
-
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEvent.java
new file mode 100644
index 0000000..011f54c
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.subscription.events.bcd;
+
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+
+public interface BCDEvent extends SubscriptionBaseEvent {
+
+ public Integer getBillCycleDayLocal();
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java
new file mode 100644
index 0000000..7c0926d
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventBuilder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.subscription.events.bcd;
+
+import org.killbill.billing.subscription.events.EventBaseBuilder;
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+
+public class BCDEventBuilder extends EventBaseBuilder<BCDEventBuilder> {
+
+ private Integer billCycleDayLocal;
+
+ public BCDEventBuilder() {
+ super();
+ }
+
+
+ public BCDEventBuilder(final BCDEvent event) {
+ super(event);
+ this.billCycleDayLocal = event.getBillCycleDayLocal();
+ }
+
+ public BCDEventBuilder(final EventBaseBuilder<?> base) {
+ super(base);
+ }
+
+
+ @Override
+ public SubscriptionBaseEvent build() {
+ return new BCDEventData(this);
+ }
+
+ public Integer getBillCycleDayLocal() {
+ return billCycleDayLocal;
+ }
+
+ public BCDEventBuilder setBillCycleDayLocal(final Integer billCycleDayLocal) {
+ this.billCycleDayLocal = billCycleDayLocal;
+ return this;
+ }
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java
new file mode 100644
index 0000000..fd69b89
--- /dev/null
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/bcd/BCDEventData.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 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.subscription.events.bcd;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.subscription.events.EventBase;
+
+public class BCDEventData extends EventBase implements BCDEvent {
+
+ private final Integer billCycleDayLocal;
+
+ public BCDEventData(final BCDEventBuilder builder) {
+ super(builder);
+ this.billCycleDayLocal = builder.getBillCycleDayLocal();
+ }
+
+ @Override
+ public EventType getType() {
+ return EventType.BCD_UPDATE;
+ }
+
+ @Override
+ public String toString() {
+ return "BCDEventData {" +
+ "uuid=" + getId() +
+ ", subscriptionId=" + getSubscriptionId() +
+ ", createdDate=" + getCreatedDate() +
+ ", updatedDate=" + getUpdatedDate() +
+ ", effectiveDate=" + getEffectiveDate() +
+ ", totalOrdering=" + getTotalOrdering() +
+ ", activeVersion=" + getActiveVersion() +
+ ", isActive=" + isActive() +
+ '}';
+ }
+
+ // Hack until we introduce a proper field for that
+ @Override
+ public Integer getBillCycleDayLocal() {
+ return billCycleDayLocal;
+ }
+
+ public static BCDEvent createBCDEvent(final DefaultSubscriptionBase subscription, final DateTime effectiveDate, final int billCycleDayLocal) {
+ return new BCDEventData(new BCDEventBuilder()
+ .setSubscriptionId(subscription.getId())
+ .setEffectiveDate(effectiveDate)
+ .setActive(true)
+ .setActiveVersion(subscription.getActiveVersion())
+ .setBillCycleDayLocal(billCycleDayLocal));
+ }
+
+}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java
index 1f81e63..934e720 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/events/SubscriptionBaseEvent.java
@@ -27,7 +27,8 @@ public interface SubscriptionBaseEvent extends Comparable<SubscriptionBaseEvent>
public enum EventType {
API_USER,
- PHASE
+ PHASE,
+ BCD_UPDATE
}
public EventType getType();
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
index 30137f4..d1e8d87 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/ddl.sql
@@ -4,7 +4,7 @@ DROP TABLE IF EXISTS subscription_events;
CREATE TABLE subscription_events (
record_id serial unique,
id varchar(36) NOT NULL,
- event_type varchar(9) NOT NULL,
+ event_type varchar(15) NOT NULL,
user_type varchar(25) DEFAULT NULL,
requested_date datetime NOT NULL,
effective_date datetime NOT NULL,
@@ -13,6 +13,7 @@ CREATE TABLE subscription_events (
phase_name varchar(128) DEFAULT NULL,
price_list_name varchar(64) DEFAULT NULL,
current_version int DEFAULT 1,
+ billing_cycle_day_local int DEFAULT NULL,
is_active boolean default true,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index 1bc98e2..35cde31 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -23,6 +23,7 @@ tableFields(prefix) ::= <<
, <prefix> phase_name
, <prefix> price_list_name
, <prefix> current_version
+, <prefix> billing_cycle_day_local
, <prefix> is_active
, <prefix> created_by
, <prefix> created_date
@@ -40,6 +41,7 @@ tableValues() ::= <<
, :phaseName
, :priceListName
, :currentVersion
+, :billingCycleDayLocal
, :isActive
, :createdBy
, :createdDate
@@ -60,6 +62,7 @@ id = :id
;
>>
+
unactiveEvent() ::= <<
update <tableName()>
set
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
index cee8006..772055c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/TestEventJson.java
@@ -37,7 +37,7 @@ public class TestEventJson extends SubscriptionTestSuiteNoDB {
public void testSubscriptionEvent() throws Exception {
final EffectiveSubscriptionInternalEvent e = new DefaultEffectiveSubscriptionEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new DateTime(),
- EntitlementState.ACTIVE, "pro", "TRIAL", "DEFAULT", EntitlementState.CANCELLED, null, null, null, 3L,
+ EntitlementState.ACTIVE, "pro", "TRIAL", "DEFAULT", null, EntitlementState.CANCELLED, null, null, null, null, 3L,
SubscriptionBaseTransitionType.CANCEL, 0, new DateTime(), 1L, 2L, null);
final String json = mapper.writeValueAsString(e);
@@ -46,4 +46,17 @@ public class TestEventJson extends SubscriptionTestSuiteNoDB {
final Object obj = mapper.readValue(json, claz);
Assert.assertTrue(obj.equals(e));
}
+
+ // Verify deserialization will work when we miss fields (previousBillCycleDayLocal, nextBillCycleDayLocal)
+ @Test(groups = "fast")
+ public void testSubscriptionEventWithNoBillCycleDayLocal() throws Exception {
+
+ final String json = "{\"eventId\":\"9e901bbc-bbcb-4f0a-8511-e58029bbea91\",\"subscriptionId\":\"c373056c-bb0c-4562-ab06-f595176aa4ae\",\"bundleId\":\"f61536b1-fc76-4337-b1e8-e38383894352\",\"effectiveTransitionTime\":\"2016-05-26T23:02:20.322Z\",\"previousState\":\"ACTIVE\",\"previousPlan\":\"pro\",\"previousPhase\":\"TRIAL\",\"previousPriceList\":\"DEFAULT\",\"nextState\":\"CANCELLED\",\"nextPlan\":null,\"nextPhase\":null,\"nextPriceList\":null,\"totalOrdering\":3,\"transitionType\":\"CANCEL\",\"remainingEventsForUserOperation\":0,\"startDate\":\"2016-05-26T23:02:20.322Z\",\"searchKey1\":1,\"searchKey2\":2,\"userToken\":null,\"requestedTransitionTime\":\"2016-05-26T23:02:20.322Z\"}";
+ final Class<?> claz = Class.forName(DefaultEffectiveSubscriptionEvent.class.getName());
+ final DefaultEffectiveSubscriptionEvent obj = (DefaultEffectiveSubscriptionEvent) mapper.readValue(json, claz);
+
+ Assert.assertEquals(obj.getId(), UUID.fromString("9e901bbc-bbcb-4f0a-8511-e58029bbea91"));
+ Assert.assertNull(obj.getPreviousBillCycleDayLocal());
+ Assert.assertNull(obj.getNextBillCycleDayLocal());
+ }
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
index 721183d..2114194 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/transfer/TestDefaultSubscriptionTransferApi.java
@@ -187,6 +187,11 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
}
@Override
+ public Integer getBillCycleDayLocal() {
+ return null;
+ }
+
+ @Override
public UUID getEventId() {
return UUID.randomUUID();
}
@@ -228,6 +233,11 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
}
@Override
+ public Integer getBillCycleDayLocal() {
+ return null;
+ }
+
+ @Override
public UUID getEventId() {
return UUID.randomUUID();
}
@@ -267,6 +277,11 @@ public class TestDefaultSubscriptionTransferApi extends SubscriptionTestSuiteNoD
}
@Override
+ public Integer getBillCycleDayLocal() {
+ return null;
+ }
+
+ @Override
public UUID getEventId() {
return UUID.randomUUID();
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
index f7947f5..da61266 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/MockSubscriptionDaoMemory.java
@@ -537,4 +537,10 @@ public class MockSubscriptionDaoMemory extends MockEntityDaoBase<SubscriptionBun
@Override
public void updateBundleExternalKey(final UUID bundleId, final String externalKey, final InternalCallContext context) {
}
+
+ @Override
+ public void createBCDChangeEvent(final DefaultSubscriptionBase subscription, final SubscriptionBaseEvent bcdEvent, final InternalCallContext context) {
+
+ }
+
}
diff --git a/util/src/test/java/org/killbill/billing/api/TestApiListener.java b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
index cff1e27..83283ef 100644
--- a/util/src/test/java/org/killbill/billing/api/TestApiListener.java
+++ b/util/src/test/java/org/killbill/billing/api/TestApiListener.java
@@ -64,7 +64,7 @@ public class TestApiListener {
private static final Joiner SPACE_JOINER = Joiner.on(" ");
- private static final long DELAY = 25000;
+ private static final long DELAY = 25000 * 1000;
private final List<NextEvent> nextExpectedEvent;
private final IDBI idbi;
@@ -129,6 +129,7 @@ public class TestApiListener {
TAG,
TAG_DEFINITION,
CUSTOM_FIELD,
+ BCD_CHANGE
}
@@ -202,6 +203,10 @@ public class TestApiListener {
assertEqualsNicely(NextEvent.PHASE);
notifyIfStackEmpty();
break;
+ case BCD_CHANGE:
+ assertEqualsNicely(NextEvent.BCD_CHANGE);
+ notifyIfStackEmpty();
+ break;
default:
throw new RuntimeException("Unexpected event type " + eventEffective.getRequestedTransitionTime());
}
diff --git a/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java b/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
index 793f2f0..60f76fa 100644
--- a/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
+++ b/util/src/test/java/org/killbill/billing/mock/MockEffectiveSubscriptionEvent.java
@@ -39,10 +39,12 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
private final DateTime effectiveTransitionTime;
private final EntitlementState previousState;
private final String previousPriceList;
+ private final Integer previousBillCycleDayLocal;
private final String previousPlan;
private final String previousPhase;
private final EntitlementState nextState;
private final String nextPriceList;
+ private final Integer nextBillCycleDayLocal;
private final String nextPlan;
private final String nextPhase;
private final Integer remainingEventsForUserOperation;
@@ -61,10 +63,12 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
@JsonProperty("previousPlan") final String previousPlan,
@JsonProperty("previousPhase") final String previousPhase,
@JsonProperty("previousPriceList") final String previousPriceList,
+ @JsonProperty("previousBillCycleDayLocal") final Integer previousBillCycleDayLocal,
@JsonProperty("nextState") final EntitlementState nextState,
@JsonProperty("nextPlan") final String nextPlan,
@JsonProperty("nextPhase") final String nextPhase,
@JsonProperty("nextPriceList") final String nextPriceList,
+ @JsonProperty("nextBillCycleDayLocal") final Integer nextBillCycleDayLocal,
@JsonProperty("totalOrdering") final Long totalOrdering,
@JsonProperty("transitionType") final SubscriptionBaseTransitionType transitionType,
@JsonProperty("remainingEventsForUserOperation") final Integer remainingEventsForUserOperation,
@@ -80,11 +84,13 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
this.effectiveTransitionTime = effectiveTransitionTime;
this.previousState = previousState;
this.previousPriceList = previousPriceList;
+ this.previousBillCycleDayLocal = previousBillCycleDayLocal;
this.previousPlan = previousPlan;
this.previousPhase = previousPhase;
this.nextState = nextState;
this.nextPlan = nextPlan;
this.nextPriceList = nextPriceList;
+ this.nextBillCycleDayLocal = nextBillCycleDayLocal;
this.nextPhase = nextPhase;
this.totalOrdering = totalOrdering;
this.userToken = userToken;
@@ -132,6 +138,11 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
}
@Override
+ public Integer getPreviousBillCycleDayLocal() {
+ return previousBillCycleDayLocal;
+ }
+
+ @Override
public String getNextPlan() {
return nextPlan;
}
@@ -158,6 +169,11 @@ public class MockEffectiveSubscriptionEvent extends BusEventBase implements Effe
}
@Override
+ public Integer getNextBillCycleDayLocal() {
+ return nextBillCycleDayLocal;
+ }
+
+ @Override
public Integer getRemainingEventsForUserOperation() {
return remainingEventsForUserOperation;
}