use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionBaseCreateApi method verifyAndBuildSubscriptionSpecifiers.
private List<SubscriptionSpecifier> verifyAndBuildSubscriptionSpecifiers(final SubscriptionBaseBundle bundle, final boolean hasBaseOrStandalonePlanSpecifier, final List<EntitlementSpecifier> entitlements, final List<Plan> entitlementsPlans, final boolean isMigrated, final DateTime effectiveDate, final SubscriptionCatalog catalog, final AddonUtils addonUtils, final TenantContext callContext, final InternalCallContext context) throws SubscriptionBaseApiException, CatalogApiException {
final List<SubscriptionSpecifier> subscriptions = new ArrayList<SubscriptionSpecifier>();
for (int i = 0; i < entitlements.size(); i++) {
final EntitlementSpecifier entitlement = entitlements.get(i);
final PlanPhaseSpecifier spec = entitlement.getPlanPhaseSpecifier();
if (spec == null) {
// BP already exists
continue;
}
final Plan plan = entitlementsPlans.get(i);
final PlanPhase phase = plan.getAllPhases()[0];
if (phase == null) {
throw new SubscriptionBaseError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog", spec.getProductName(), spec.getBillingPeriod().toString(), plan.getPriceList()));
}
// verify the number of subscriptions (of the same kind) allowed per bundle and the existing ones
if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0) {
// TODO We should also look to the specifiers being created for validation
final List<DefaultSubscriptionBase> subscriptionsForBundle = getSubscriptionsForBundle(bundle.getId(), null, catalog, addonUtils, callContext, context);
final int existingAddOnsWithSamePlanName = addonUtils.countExistingAddOnsWithSamePlanName(subscriptionsForBundle, plan.getName());
final int currentAddOnsWithSamePlanName = countCurrentAddOnsWithSamePlanName(entitlementsPlans, plan);
if ((existingAddOnsWithSamePlanName + currentAddOnsWithSamePlanName) > plan.getPlansAllowedInBundle()) {
// a new ADD_ON subscription of the same plan can't be added because it has reached its limit by bundle
throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
}
}
}
final DateTime bundleStartDate;
if (hasBaseOrStandalonePlanSpecifier) {
bundleStartDate = effectiveDate;
} else {
final SubscriptionBase baseSubscription = dao.getBaseSubscription(bundle.getId(), catalog, context);
bundleStartDate = getBundleStartDateWithSanity(bundle.getId(), baseSubscription, plan, effectiveDate, addonUtils, context);
}
final SubscriptionSpecifier subscription = new SubscriptionSpecifier();
subscription.setRealPriceList(plan.getPriceList().getName());
subscription.setEffectiveDate(effectiveDate);
subscription.setProcessedDate(context.getCreatedDate());
subscription.setPlan(plan);
subscription.setInitialPhase(spec.getPhaseType());
subscription.setBuilder(new SubscriptionBuilder().setId(UUIDs.randomUUID()).setBundleId(bundle.getId()).setExternalKey(entitlement.getExternalKey()).setBundleExternalKey(bundle.getExternalKey()).setCategory(plan.getProduct().getCategory()).setBundleStartDate(bundleStartDate).setAlignStartDate(effectiveDate).setMigrated(isMigrated).setSubscriptionBCD(entitlement.getBillCycleDay()));
subscriptions.add(subscription);
}
return subscriptions;
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionDao method buildBundleSubscriptions.
private List<DefaultSubscriptionBase> buildBundleSubscriptions(final List<DefaultSubscriptionBase> input, @Nullable final Multimap<UUID, SubscriptionBaseEvent> eventsForSubscription, @Nullable final Collection<SubscriptionBaseEvent> dryRunEvents, final SubscriptionCatalog catalog, final InternalTenantContext context) throws CatalogApiException {
if (input == null || input.isEmpty()) {
return Collections.emptyList();
}
// Make sure BasePlan -- if exists-- is first
Collections.sort(input, DefaultSubscriptionInternalApi.SUBSCRIPTIONS_COMPARATOR);
final Collection<ApiEventChange> baseChangeEvents = new LinkedList<ApiEventChange>();
ApiEventCancel baseCancellationEvent = null;
final List<DefaultSubscriptionBase> result = new ArrayList<DefaultSubscriptionBase>(input.size());
for (final DefaultSubscriptionBase cur : input) {
final List<SubscriptionBaseEvent> events = eventsForSubscription != null ? (List<SubscriptionBaseEvent>) eventsForSubscription.get(cur.getId()) : getEventsForSubscription(cur.getId(), context);
mergeDryRunEvents(cur.getId(), events, dryRunEvents);
DefaultSubscriptionBase reloaded = createSubscriptionForInternalUse(cur, events, catalog, context);
switch(cur.getCategory()) {
case BASE:
for (final SubscriptionBaseEvent event : events) {
if (!event.isActive()) {
continue;
} else if (event instanceof ApiEventCancel) {
baseCancellationEvent = (ApiEventCancel) event;
break;
} else if (event instanceof ApiEventChange) {
// Need to track all changes, see https://github.com/killbill/killbill/issues/268
baseChangeEvents.add((ApiEventChange) event);
}
}
break;
case ADD_ON:
final Plan targetAddOnPlan = reloaded.getCurrentPlan();
if (targetAddOnPlan == null || reloaded.getFutureEndDate() != null) {
// triggers another cancellation before?
break;
}
SubscriptionBaseEvent baseTriggerEventForAddOnCancellation = baseCancellationEvent;
for (final ApiEventChange baseChangeEvent : baseChangeEvents) {
final Plan basePlan = catalog.findPlan(baseChangeEvent.getEventPlan(), baseChangeEvent.getEffectiveDate(), cur.getAlignStartDate());
final Product baseProduct = basePlan.getProduct();
if ((!addonUtils.isAddonAvailable(baseProduct, targetAddOnPlan)) || (addonUtils.isAddonIncluded(baseProduct, targetAddOnPlan))) {
if (baseTriggerEventForAddOnCancellation != null) {
if (baseTriggerEventForAddOnCancellation.getEffectiveDate().isAfter(baseChangeEvent.getEffectiveDate())) {
baseTriggerEventForAddOnCancellation = baseChangeEvent;
}
} else {
baseTriggerEventForAddOnCancellation = baseChangeEvent;
}
}
}
if (baseTriggerEventForAddOnCancellation != null) {
final SubscriptionBaseEvent addOnCancelEvent = new ApiEventCancel(new ApiEventBuilder().setSubscriptionId(reloaded.getId()).setEffectiveDate(baseTriggerEventForAddOnCancellation.getEffectiveDate()).setCreatedDate(baseTriggerEventForAddOnCancellation.getCreatedDate()).setFromDisk(false));
events.add(addOnCancelEvent);
// Finally reload subscription with full set of events
reloaded = createSubscriptionForInternalUse(cur, events, catalog, context);
}
break;
default:
break;
}
result.add(reloaded);
}
return result;
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class DefaultSubscriptionDao method getSubscriptionsFromAccountId.
public Map<UUID, List<DefaultSubscriptionBase>> getSubscriptionsFromAccountId(@Nullable final LocalDate cutoffDt, final InternalTenantContext context) {
final List<DefaultSubscriptionBase> allSubscriptions = transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<DefaultSubscriptionBase>>() {
@Override
public List<DefaultSubscriptionBase> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
final SubscriptionSqlDao subscriptionSqlDao = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
final List<SubscriptionModelDao> subscriptionModels = cutoffDt == null ? subscriptionSqlDao.getByAccountRecordId(context) : subscriptionSqlDao.getActiveByAccountRecordId(cutoffDt.toDate(), context);
// We avoid pulling the bundles when a cutoffDt is specified, as those are not really used
final List<SubscriptionBundleModelDao> bundleModels = cutoffDt == null ? entitySqlDaoWrapperFactory.become(BundleSqlDao.class).getByAccountRecordId(context) : ImmutableList.of();
return new ArrayList<DefaultSubscriptionBase>(Collections2.transform(subscriptionModels, new Function<SubscriptionModelDao, DefaultSubscriptionBase>() {
@Override
public DefaultSubscriptionBase apply(final SubscriptionModelDao input) {
final SubscriptionBundleModelDao bundleModel = Iterables.tryFind(bundleModels, new Predicate<SubscriptionBundleModelDao>() {
@Override
public boolean apply(final SubscriptionBundleModelDao bundleInput) {
return bundleInput.getId().equals(input.getBundleId());
}
}).orNull();
final String bundleExternalKey = bundleModel != null ? bundleModel.getExternalKey() : null;
return SubscriptionModelDao.toSubscription(input, bundleExternalKey);
}
}));
}
});
final Map<UUID, List<DefaultSubscriptionBase>> result = new HashMap<UUID, List<DefaultSubscriptionBase>>();
for (final DefaultSubscriptionBase subscriptionBase : allSubscriptions) {
if (result.get(subscriptionBase.getBundleId()) == null) {
result.put(subscriptionBase.getBundleId(), new LinkedList<DefaultSubscriptionBase>());
}
result.get(subscriptionBase.getBundleId()).add(subscriptionBase);
}
return result;
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class MockSubscriptionDaoMemory method updateChargedThroughDate.
public void updateChargedThroughDate(final DefaultSubscriptionBase subscription, final InternalCallContext context) {
boolean found = false;
final Iterator<DefaultSubscriptionBase> it = subscriptions.iterator();
while (it.hasNext()) {
final SubscriptionBase cur = it.next();
if (cur.getId().equals(subscription.getId())) {
found = true;
it.remove();
break;
}
}
if (found) {
subscriptions.add(subscription);
}
}
use of org.killbill.billing.subscription.api.user.DefaultSubscriptionBase in project killbill by killbill.
the class TestSubscriptionDao method afterMethod.
// to ignore events
@Override
@AfterMethod(groups = "slow")
public void afterMethod() throws Exception {
if (hasFailed()) {
final String externalKey = "12345";
final DateTime startDate = clock.getUTCNow();
final DateTime createdDate = startDate.plusSeconds(10);
final DefaultSubscriptionBaseBundle bundleDef = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
final SubscriptionBaseBundle bundle = dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
final List<SubscriptionBaseBundle> result = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
// Operation succeeds but nothing new got created because bundle is empty
dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
assertEquals(result2.size(), 1);
// Create a subscription and this time operation should fail
final SubscriptionBuilder builder = new SubscriptionBuilder().setId(UUIDs.randomUUID()).setBundleId(bundle.getId()).setBundleExternalKey(bundle.getExternalKey()).setCategory(ProductCategory.BASE).setBundleStartDate(startDate).setAlignStartDate(startDate).setMigrated(false);
final ApiEventBuilder createBuilder = new ApiEventBuilder().setSubscriptionId(builder.getId()).setEventPlan("shotgun-monthly").setEventPlanPhase("shotgun-monthly-trial").setEventPriceList(DefaultPriceListSet.DEFAULT_PRICELIST_NAME).setEffectiveDate(startDate).setFromDisk(true);
final SubscriptionBaseEvent creationEvent = new ApiEventCreate(createBuilder);
final DefaultSubscriptionBase subscription = new DefaultSubscriptionBase(builder);
testListener.pushExpectedEvents(NextEvent.CREATE);
final SubscriptionBaseWithAddOns subscriptionBaseWithAddOns = new DefaultSubscriptionBaseWithAddOns(bundle, ImmutableList.<SubscriptionBase>of(subscription));
dao.createSubscriptionsWithAddOns(ImmutableList.<SubscriptionBaseWithAddOns>of(subscriptionBaseWithAddOns), ImmutableMap.<UUID, List<SubscriptionBaseEvent>>of(subscription.getId(), ImmutableList.<SubscriptionBaseEvent>of(creationEvent)), catalog, internalCallContext);
assertListenerStatus();
// Operation Should now fail
try {
dao.createSubscriptionBundle(bundleDef, catalog, true, internalCallContext);
Assert.fail("Should fail to create new subscription bundle with existing key");
} catch (SubscriptionBaseApiException e) {
assertEquals(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS.getCode(), e.getCode());
}
return;
}
subscriptionTestInitializer.stopTestFramework(testListener, busService, subscriptionBaseService);
}
Aggregations