/** * Given a set of billing events, add corresponding blocking (overdue) billing events. * * @param billingEvents the original list of billing events to update (without overdue events) */ public void insertBlockingEvents(final SortedSet<BillingEvent> billingEvents, final InternalTenantContext context) { if (billingEvents.size() <= 0) { return; } final Account account = billingEvents.first().getAccount(); final Hashtable<UUID, List<SubscriptionBase>> bundleMap = createBundleSubscriptionMap(billingEvents); final SortedSet<BillingEvent> billingEventsToAdd = new TreeSet<BillingEvent>(); final SortedSet<BillingEvent> billingEventsToRemove = new TreeSet<BillingEvent>(); final List<BlockingState> blockingEvents = blockingApi.getBlockingAllForAccount(context); final List<DisabledDuration> blockingDurations = createBlockingDurations(blockingEvents); for (final UUID bundleId : bundleMap.keySet()) { for (final SubscriptionBase subscription : bundleMap.get(bundleId)) { billingEventsToAdd.addAll(createNewEvents(blockingDurations, billingEvents, account, subscription)); billingEventsToRemove.addAll(eventsToRemove(blockingDurations, billingEvents, subscription)); } } for (final BillingEvent eventToAdd : billingEventsToAdd) { billingEvents.add(eventToAdd); } for (final BillingEvent eventToRemove : billingEventsToRemove) { billingEvents.remove(eventToRemove); } }
protected SortedSet<BillingEvent> createNewEvents(final List<DisabledDuration> disabledDuration, final SortedSet<BillingEvent> billingEvents, final Account account, final SubscriptionBase subscription) { final SortedSet<BillingEvent> result = new TreeSet<BillingEvent>(); for (final DisabledDuration duration : disabledDuration) { // The first one before the blocked duration final BillingEvent precedingInitialEvent = precedingBillingEventForSubscription(duration.getStart(), billingEvents, subscription); // The last one during of before the duration final BillingEvent precedingFinalEvent = precedingBillingEventForSubscription(duration.getEnd(), billingEvents, subscription); if (precedingInitialEvent != null) { // there is a preceding billing event result.add(createNewDisableEvent(duration.getStart(), precedingInitialEvent)); if (duration.getEnd() != null) { // no second event in the pair means they are still disabled (no re-enable) result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent)); } } else if (precedingFinalEvent != null) { // can happen - e.g. phase event result.add(createNewReenableEvent(duration.getEnd(), precedingFinalEvent)); } // N.B. if there's no precedingInitial and no precedingFinal then there's nothing to do } return result; }
protected BillingEvent precedingBillingEventForSubscription(final DateTime datetime, final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription) { if (datetime == null) { //second of a pair can be null if there's no re-enabling return null; } final SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription); BillingEvent result = filteredBillingEvents.first(); if (datetime.isBefore(result.getEffectiveDate())) { //This case can happen, for example, if we have an add on and the bundle goes into disabled before the add on is created return null; } for (final BillingEvent event : filteredBillingEvents) { if (!event.getEffectiveDate().isBefore(datetime)) { // found it its the previous event return result; } else { // still looking result = event; } } return result; }
@Test(groups = "fast") public void testEventsToRemoveOpenFollow() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, null)); final BillingEvent e1 = createRealEvent(now.plusDays(1), subscription1); billingEvents.add(e1); final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 1); assertEquals(results.first(), e1); }
@Test(groups = "fast") public void testCreateNewEventsClosedFollow() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); billingEvents.add(createRealEvent(now.plusDays(3), subscription1)); final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); assertEquals(results.size(), 0); }
blockingCalculator.insertBlockingEvents(billingEvents, internalCallContext); final SortedSet<BillingEvent> s1Events = blockingCalculator.filter(billingEvents, subscription1); final Iterator<BillingEvent> it1 = s1Events.iterator(); assertEquals(it1.next(), A); assertEquals(it1.next().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED); final SortedSet<BillingEvent> s2Events = blockingCalculator.filter(billingEvents, subscription2); final Iterator<BillingEvent> it2 = s2Events.iterator(); assertEquals(it2.next(), B); assertEquals(it2.next().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED); final SortedSet<BillingEvent> s3Events = blockingCalculator.filter(billingEvents, subscription3); final Iterator<BillingEvent> it3 = s3Events.iterator(); assertEquals(it3.next(), D);
@Test(groups = "fast") public void testCreateNewDisableEvent() { final DateTime now = clock.getUTCNow(); final BillingEvent event = new MockBillingEvent(); final BillingEvent result = blockingCalculator.createNewDisableEvent(now, event); assertEquals(result.getBillCycleDayLocal(), event.getBillCycleDayLocal()); assertEquals(result.getEffectiveDate(), now); assertEquals(result.getPlanPhase(), event.getPlanPhase()); assertEquals(result.getPlan(), event.getPlan()); assertNull(result.getFixedPrice()); assertNull(result.getRecurringPrice()); assertEquals(result.getCurrency(), event.getCurrency()); assertEquals(result.getDescription(), ""); assertEquals(result.getBillingMode(), event.getBillingMode()); assertEquals(result.getBillingPeriod(), BillingPeriod.NO_BILLING_PERIOD); assertEquals(result.getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED); // TODO - ugly, fragile assertEquals(result.getTotalOrdering(), (Long) (BlockingCalculator.getGlobalTotalOrder().get() - 1)); }
@Test(groups = "fast") public void testCreateNewReenableEvent() { final DateTime now = clock.getUTCNow(); final BillingEvent event = new MockBillingEvent(); final BillingEvent result = blockingCalculator.createNewReenableEvent(now, event); assertEquals(result.getBillCycleDayLocal(), event.getBillCycleDayLocal()); assertEquals(result.getEffectiveDate(), now); assertEquals(result.getPlanPhase(), event.getPlanPhase()); assertEquals(result.getPlan(), event.getPlan()); assertEquals(result.getFixedPrice(), event.getFixedPrice()); assertEquals(result.getRecurringPrice(), event.getRecurringPrice()); assertEquals(result.getCurrency(), event.getCurrency()); assertEquals(result.getDescription(), ""); assertEquals(result.getBillingMode(), event.getBillingMode()); assertEquals(result.getBillingPeriod(), event.getBillingPeriod()); assertEquals(result.getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED); // TODO - ugly, fragile assertEquals(result.getTotalOrdering(), (Long) (BlockingCalculator.getGlobalTotalOrder().get() - 1)); }
@Override public BillingEventSet getBillingEventsForAccountAndUpdateAccountBCD(final UUID accountId, final InternalCallContext context) { final List<SubscriptionBaseBundle> bundles = subscriptionApi.getBundlesForAccount(accountId, context); final DefaultBillingEventSet result = new DefaultBillingEventSet(); try { final Account account = accountApi.getAccountById(accountId, context); // Check to see if billing is off for the account final List<Tag> accountTags = tagApi.getTags(accountId, ObjectType.ACCOUNT, context); final boolean found_AUTO_INVOICING_OFF = is_AUTO_INVOICING_OFF(accountTags); if (found_AUTO_INVOICING_OFF) { result.setAccountAutoInvoiceIsOff(true); return result; // billing is off, we are done } addBillingEventsForBundles(bundles, account, context, result); } catch (AccountApiException e) { log.warn("Failed while getting BillingEvent", e); } // Pretty-print the events, before and after the blocking calculator does its magic final StringBuilder logStringBuilder = new StringBuilder("Computed billing events for accountId ").append(accountId); eventsToString(logStringBuilder, result, "\nBilling Events Raw"); blockCalculator.insertBlockingEvents(result, context); eventsToString(logStringBuilder, result, "\nBilling Events After Blocking"); log.info(logStringBuilder.toString()); return result; }
@Test(groups = "fast") public void testCreateBundleSubscriptionMap() { final SortedSet<BillingEvent> events = new TreeSet<BillingEvent>(); events.add(createBillingEvent(subscription1)); events.add(createBillingEvent(subscription2)); events.add(createBillingEvent(subscription3)); events.add(createBillingEvent(subscription4)); final Hashtable<UUID, List<SubscriptionBase>> map = blockingCalculator.createBundleSubscriptionMap(events); assertNotNull(map); assertEquals(map.keySet().size(), 2); assertEquals(map.get(bundleId1).size(), 3); assertEquals(map.get(bundleId2).size(), 1); }
@Test(groups = "fast") public void testPrecedingBillingEventForSubscription() { final DateTime now = new DateTime(); final SortedSet<BillingEvent> events = new TreeSet<BillingEvent>(); events.add(createRealEvent(now.minusDays(10), subscription1)); events.add(createRealEvent(now.minusDays(6), subscription1)); events.add(createRealEvent(now.minusDays(5), subscription1)); events.add(createRealEvent(now.minusDays(1), subscription1)); final BillingEvent minus11 = blockingCalculator.precedingBillingEventForSubscription(now.minusDays(11), events, subscription1); assertNull(minus11); final BillingEvent minus5andAHalf = blockingCalculator.precedingBillingEventForSubscription(now.minusDays(5).minusHours(12), events, subscription1); assertNotNull(minus5andAHalf); assertEquals(minus5andAHalf.getEffectiveDate(), now.minusDays(6)); }
List<DisabledDuration> pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertNotNull(pairs.get(0).getStart()); pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertNotNull(pairs.get(0).getStart()); pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertNotNull(pairs.get(0).getStart()); pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertNotNull(pairs.get(0).getStart()); pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertNotNull(pairs.get(0).getStart()); pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertEquals(pairs.get(0).getStart(), now.plusDays(1)); pairs = blockingCalculator.createBlockingDurations(blockingEvents); assertEquals(pairs.size(), 1); assertEquals(pairs.get(0).getStart(), now.plusDays(1));
protected List<DisabledDuration> createBlockingDurations(final Iterable<BlockingState> overdueBundleEvents) { final List<DisabledDuration> result = new ArrayList<BlockingCalculator.DisabledDuration>(); // Earliest blocking event BlockingState first = null; int blockedNesting = 0; BlockingState lastOne = null; for (final BlockingState e : overdueBundleEvents) { lastOne = e; if (e.isBlockBilling() && blockedNesting == 0) { // First blocking event of contiguous series of blocking events first = e; blockedNesting++; } else if (e.isBlockBilling() && blockedNesting > 0) { // Nest blocking states blockedNesting++; } else if (!e.isBlockBilling() && blockedNesting > 0) { blockedNesting--; if (blockedNesting == 0) { // End of the interval addDisabledDuration(result, first, e); first = null; } } } if (first != null) { // found a transition to disabled with no terminating event addDisabledDuration(result, first, lastOne.isBlockBilling() ? null : lastOne); } return result; }
@Test(groups = "fast") public void testEventsToRemoveClosedBetwn() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); final BillingEvent e2 = createRealEvent(now.plusDays(1), subscription1); billingEvents.add(e2); final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 1); assertEquals(results.first(), e2); }
@Test(groups = "fast") public void testCreateNewEventsOpenFollow() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, null)); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); assertEquals(results.size(), 0); }
@Test(groups = "fast") public void testSimpleWithClearBlockingDuration() throws Exception { final UUID ovdId = UUID.randomUUID(); final BillingEvent trial = createRealEvent(new LocalDate(2012, 5, 1).toDateTimeAtStartOfDay(DateTimeZone.UTC), subscription1, SubscriptionBaseTransitionType.CREATE); final BillingEvent phase = createRealEvent(new LocalDate(2012, 5, 31).toDateTimeAtStartOfDay(DateTimeZone.UTC), subscription1, SubscriptionBaseTransitionType.PHASE); final BillingEvent upgrade = createRealEvent(new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC), subscription1, SubscriptionBaseTransitionType.CHANGE); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); billingEvents.add(trial); billingEvents.add(phase); billingEvents.add(upgrade); final List<BlockingState> blockingEvents = new ArrayList<BlockingState>(); blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, false, false, new LocalDate(2012, 7, 5).toDateTimeAtStartOfDay(DateTimeZone.UTC))); blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 15).toDateTimeAtStartOfDay(DateTimeZone.UTC))); blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, DISABLED_BUNDLE, "test", true, true, true, new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC))); blockingEvents.add(new DefaultBlockingState(ovdId, BlockingStateType.SUBSCRIPTION_BUNDLE, CLEAR_BUNDLE, "test", false, false, false, new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC))); setBlockingStates(blockingEvents); blockingCalculator.insertBlockingEvents(billingEvents, internalCallContext); assertEquals(billingEvents.size(), 5); final List<BillingEvent> events = new ArrayList<BillingEvent>(billingEvents); assertEquals(events.get(0).getEffectiveDate(), new LocalDate(2012, 5, 1).toDateTimeAtStartOfDay(DateTimeZone.UTC)); assertEquals(events.get(0).getTransitionType(), SubscriptionBaseTransitionType.CREATE); assertEquals(events.get(1).getEffectiveDate(), new LocalDate(2012, 5, 31).toDateTimeAtStartOfDay(DateTimeZone.UTC)); assertEquals(events.get(1).getTransitionType(), SubscriptionBaseTransitionType.PHASE); assertEquals(events.get(2).getEffectiveDate(), new LocalDate(2012, 7, 15).toDateTimeAtStartOfDay(DateTimeZone.UTC)); assertEquals(events.get(2).getTransitionType(), SubscriptionBaseTransitionType.START_BILLING_DISABLED); assertEquals(events.get(3).getEffectiveDate(), new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC)); assertEquals(events.get(3).getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED); assertEquals(events.get(4).getEffectiveDate(), new LocalDate(2012, 7, 25).toDateTimeAtStartOfDay(DateTimeZone.UTC)); assertEquals(events.get(4).getTransitionType(), SubscriptionBaseTransitionType.CHANGE); }
@Test(groups = "fast") public void testEventsToRemoveClosedFollow() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); final BillingEvent e3 = createRealEvent(now.plusDays(3), subscription1); billingEvents.add(e3); final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 0); }
@Test(groups = "fast") public void testCreateNewEventsClosedBetweenFollow() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); billingEvents.add(createRealEvent(now.plusDays(1), subscription1)); final SortedSet<BillingEvent> results = blockingCalculator.createNewEvents(disabledDuration, billingEvents, account, subscription1); assertEquals(results.size(), 1); assertEquals(results.last().getEffectiveDate(), now.plusDays(2)); assertEquals(results.last().getRecurringPrice(), billingEvents.first().getRecurringPrice()); assertEquals(results.last().getTransitionType(), SubscriptionBaseTransitionType.END_BILLING_DISABLED); }
protected SortedSet<BillingEvent> eventsToRemove(final List<DisabledDuration> disabledDuration, final SortedSet<BillingEvent> billingEvents, final SubscriptionBase subscription) { final SortedSet<BillingEvent> result = new TreeSet<BillingEvent>(); final SortedSet<BillingEvent> filteredBillingEvents = filter(billingEvents, subscription); for (final DisabledDuration duration : disabledDuration) { for (final BillingEvent event : filteredBillingEvents) { if (duration.getEnd() == null || event.getEffectiveDate().isBefore(duration.getEnd())) { if (event.getEffectiveDate().isAfter(duration.getStart())) { //between the pair result.add(event); } } else { //after the last event of the pair no need to keep checking break; } } } return result; }
@Test(groups = "fast") public void testEventsToRemoveClosedPrev() { final DateTime now = clock.getUTCNow(); final List<DisabledDuration> disabledDuration = new ArrayList<BlockingCalculator.DisabledDuration>(); final SortedSet<BillingEvent> billingEvents = new TreeSet<BillingEvent>(); disabledDuration.add(new DisabledDuration(now, now.plusDays(2))); final BillingEvent e1 = createRealEvent(now.minusDays(1), subscription1); billingEvents.add(e1); final SortedSet<BillingEvent> results = blockingCalculator.eventsToRemove(disabledDuration, billingEvents, subscription1); assertEquals(results.size(), 0); }