public IReadOnlyList <CostPeriod> Execute( DateTime startTimeInclusive, DateTime endTimeExclusive, IReadOnlyList <MergedSnapshot> snapshots, IReadOnlyList <CreatorPost> creatorPosts) { var result = new List <CostPeriod>(); MergedSnapshot lastSnapshot = null; foreach (var snapshot in snapshots.Concat(new List <MergedSnapshot> { null })) { if (lastSnapshot != null) { var cost = this.costCalculator.Execute(lastSnapshot, creatorPosts); if (snapshot == null) { var period = new CostPeriod(lastSnapshot.Timestamp, endTimeExclusive, cost); result.Add(period); } else { var period = new CostPeriod(lastSnapshot.Timestamp, snapshot.Timestamp, cost); result.Add(period); } } lastSnapshot = snapshot; } return(result); }
public IReadOnlyList <MergedSnapshot> Execute( UserId subscriberId, UserId creatorId, DateTime startTimeInclusive, IReadOnlyList <ISnapshot> snapshots) { if (snapshots.Count == 0) { return(new List <MergedSnapshot>()); } DateTime initialTimestamp = startTimeInclusive; var firstTimestamp = snapshots[0].Timestamp; if (firstTimestamp < startTimeInclusive) { initialTimestamp = firstTimestamp; } var creatorChannels = CreatorChannelsSnapshot.Default(initialTimestamp, creatorId); var creatorFreeAccessUsers = CreatorFreeAccessUsersSnapshot.Default(initialTimestamp, creatorId); var subscriberChannels = SubscriberChannelsSnapshot.Default(initialTimestamp, subscriberId); var subscriber = SubscriberSnapshot.Default(initialTimestamp, subscriberId); var calculatedAccountBalance = CalculatedAccountBalanceSnapshot.Default(initialTimestamp, subscriberId, LedgerAccountType.FifthweekCredit); var mergedSnapshots = new List <MergedSnapshot>(); if (firstTimestamp > initialTimestamp) { mergedSnapshots.Add(new MergedSnapshot(creatorChannels, creatorFreeAccessUsers, subscriberChannels, subscriber, calculatedAccountBalance)); } foreach (var snapshot in snapshots) { var assigned = this.TryAssign(snapshot, ref creatorChannels) || this.TryAssign(snapshot, ref creatorFreeAccessUsers) || this.TryAssign(snapshot, ref subscriberChannels) || this.TryAssign(snapshot, ref subscriber) || this.TryAssign(snapshot, ref calculatedAccountBalance); if (!assigned) { throw new InvalidOperationException("Unknown snapshot type: " + snapshot.GetType().Name); } var newMergedSnapshot = new MergedSnapshot(creatorChannels, creatorFreeAccessUsers, subscriberChannels, subscriber, calculatedAccountBalance); if (mergedSnapshots.Count > 0 && mergedSnapshots.Last().Timestamp == snapshot.Timestamp) { mergedSnapshots.RemoveAt(mergedSnapshots.Count - 1); } mergedSnapshots.Add(newMergedSnapshot); } return(mergedSnapshots); }
public int Execute(MergedSnapshot snapshot, IReadOnlyList <CreatorPost> creatorPosts) { // No subscribed channels. if (snapshot.SubscriberChannels.SubscribedChannels.Count == 0) { return(0); } // On creator guestlist. if (snapshot.Subscriber.Email != null && snapshot.CreatorFreeAccessUsers.FreeAccessUserEmails.Contains(snapshot.Subscriber.Email)) { return(0); } // No money in account. if (snapshot.CalculatedAccountBalance.Amount <= 0) { return(0); } // Get all subscribed channels... var creatorSubscriptions = from s in snapshot.SubscriberChannels.SubscribedChannels let c = snapshot.CreatorChannels.CreatorChannels.FirstOrDefault(v => v.ChannelId.Equals(s.ChannelId)) where c != null select new { Subscription = s, Channel = c }; // ...where creator has posted in the subscriber's channel billing week... var subscriptionsWithPosts = creatorPosts == null ? creatorSubscriptions : creatorSubscriptions.Where( v => this.CreatorPostedInBillingWeek( v.Subscription.SubscriptionStartDate, v.Subscription.ChannelId, creatorPosts)); // ...and the current price has been accepted... var acceptedSubscriptions = subscriptionsWithPosts.Where(v => v.Subscription.AcceptedPrice >= v.Channel.Price); // ...calculate the total price. var totalCost = acceptedSubscriptions.Aggregate(0, (a, s) => a + s.Channel.Price); return(totalCost); }
public IReadOnlyList <MergedSnapshot> Execute( DateTime startTimeInclusive, IReadOnlyList <MergedSnapshot> snapshots) { if (snapshots.Count == 0 || snapshots[0].Timestamp == startTimeInclusive) { return(snapshots); } if (snapshots[0].Timestamp > startTimeInclusive) { throw new InvalidOperationException("Initial merged snapshot is unexpectedly ahead of start time."); } // If any snapshot is located at the start time, ignore everything before. for (int i = 0; i < snapshots.Count; i++) { if (snapshots[i].Timestamp == startTimeInclusive) { return(snapshots.Skip(i).ToList()); } } // We have timestamps either side of the start time, so find the first one which is // earlier, bump it to the start time, and ignore any before. for (int i = snapshots.Count - 1; i >= 0; i--) { var snapshot = snapshots[i]; if (snapshot.Timestamp < startTimeInclusive) { var result = snapshots.Skip(i).ToList(); result[0] = new MergedSnapshot( startTimeInclusive, snapshot.CreatorChannels, snapshot.CreatorFreeAccessUsers, snapshot.SubscriberChannels, snapshot.Subscriber, snapshot.CalculatedAccountBalance); return(result); } } throw new InvalidOperationException("Unexpectedly did not find required timestamps for trimming."); }
public void WhenOnlyCalculatedAccountBalanceSnapshot_ShouldReturnMergedSnapshot() { var calculatedAccountBalanceSnapshot = new CalculatedAccountBalanceSnapshot(Now, SubscriberId1, LedgerAccountType.FifthweekCredit, 10); var expectedResult = new MergedSnapshot( Now, CreatorChannelsSnapshot.Default(Now, CreatorId1), CreatorFreeAccessUsersSnapshot.Default(Now, CreatorId1), SubscriberChannelsSnapshot.Default(Now, SubscriberId1), SubscriberSnapshot.Default(Now, SubscriberId1), calculatedAccountBalanceSnapshot); var result = this.target.Execute(SubscriberId1, CreatorId1, Now, new List <ISnapshot> { calculatedAccountBalanceSnapshot }); CollectionAssert.AreEqual(new List <MergedSnapshot> { expectedResult }, result.ToList()); }
public void WhenOnlySubscriberChannelSnapshot_ShouldReturnMergedSnapshot() { var subscriberChannelSnapshot = new SubscriberChannelsSnapshot(Now, SubscriberId1, new List <SubscriberChannelsSnapshotItem> { new SubscriberChannelsSnapshotItem(new ChannelId(Guid.NewGuid()), 100, Now) }); var expectedResult = new MergedSnapshot( Now, CreatorChannelsSnapshot.Default(Now, CreatorId1), CreatorFreeAccessUsersSnapshot.Default(Now, CreatorId1), subscriberChannelSnapshot, SubscriberSnapshot.Default(Now, SubscriberId1), CalculatedAccountBalanceSnapshot.Default(Now, SubscriberId1, LedgerAccountType.FifthweekCredit)); var result = this.target.Execute(SubscriberId1, CreatorId1, Now, new List <ISnapshot> { subscriberChannelSnapshot }); CollectionAssert.AreEqual(new List <MergedSnapshot> { expectedResult }, result.ToList()); }
/// <summary> /// This method "rolls forward" subscriptions, which means if the user unsubscribed we /// make sure the subscription is billed until the end of their billing period by extending /// the subscription length here. /// </summary> public IReadOnlyList <MergedSnapshot> Execute( DateTime endTimeExclusive, IReadOnlyList <MergedSnapshot> inputSnapshots) { var snapshots = inputSnapshots.ToList(); var activeSubscriptions = new Dictionary <ChannelId, ActiveSubscription>(); for (int i = 0; i < snapshots.Count; i++) { var snapshot = snapshots[i]; foreach (var subscription in snapshot.SubscriberChannels.SubscribedChannels) { var billingWeekEndDateExclusive = BillingWeekUtilities.CalculateBillingWeekEndDateExclusive( subscription.SubscriptionStartDate, snapshot.Timestamp); ActiveSubscription activeSubscription; if (activeSubscriptions.TryGetValue(subscription.ChannelId, out activeSubscription)) { // The following condition is a relaxation to allow people to briefly re-subscribe without // trigging a whole new billing week, and therefore to reduce anticipated complaints. // If they restarted their subscription before the minumum end date of their // previous subscription, then we do not reset the minimum end date yet. // However if they exceed that billing week then the new subscription's billing week will be used. if (snapshot.Timestamp < activeSubscription.BillingWeekEndDateExclusive) { billingWeekEndDateExclusive = activeSubscription.BillingWeekEndDateExclusive; } activeSubscription = new ActiveSubscription(billingWeekEndDateExclusive, subscription); activeSubscriptions[subscription.ChannelId] = activeSubscription; } else { activeSubscription = new ActiveSubscription(billingWeekEndDateExclusive, subscription); activeSubscriptions.Add(subscription.ChannelId, activeSubscription); } } // Copy the list so we can modify activeSubscriptions within the loop. foreach (var activeSubscription in activeSubscriptions.Values.ToList()) { var activeChannelId = activeSubscription.Subscription.ChannelId; if (snapshot.SubscriberChannels.SubscribedChannels.All(v => !v.ChannelId.Equals(activeChannelId))) { // The subscriber has unsubscribed from this channel. var billingWeekFinalSnapshotTime = this.GetBillingWeekFinalSnapshotTime(activeSubscription.BillingWeekEndDateExclusive); var subscriptionDuration = snapshot.Timestamp - activeSubscription.Subscription.SubscriptionStartDate; if (subscriptionDuration.TotalDays > DaysInWeek) { // We have exceeded the minimum subscripion period, so there is nothing to adjust. activeSubscriptions.Remove(activeSubscription.Subscription.ChannelId); } else if (snapshot.Timestamp == billingWeekFinalSnapshotTime) { // We are at the end of the billing week, so there is nothing to adjust. activeSubscriptions.Remove(activeSubscription.Subscription.ChannelId); } else if (snapshot.Timestamp < billingWeekFinalSnapshotTime) { if (snapshot.CreatorChannels.CreatorChannels.All(v => !v.ChannelId.Equals(activeChannelId))) { // We are within the billing week, but the channel is no longer published. // So we stop billing at this point. activeSubscriptions.Remove(activeSubscription.Subscription.ChannelId); } else { // We are still within their billing week and the creator still publishes the channel. // We must extend their subscription to this snapshot. var newSnapshot = new MergedSnapshot( snapshot.Timestamp, snapshot.CreatorChannels, snapshot.CreatorFreeAccessUsers, new SubscriberChannelsSnapshot( snapshot.SubscriberChannels.Timestamp, snapshot.SubscriberChannels.SubscriberId, snapshot.SubscriberChannels.SubscribedChannels.Concat(new[] { activeSubscription.Subscription }).ToList()), snapshot.Subscriber, snapshot.CalculatedAccountBalance); snapshots[i] = newSnapshot; if ((i == snapshots.Count - 1 && activeSubscription.BillingWeekEndDateExclusive < endTimeExclusive) || (i < snapshots.Count - 1 && snapshots[i + 1].Timestamp > billingWeekFinalSnapshotTime)) { // Either this is the last snapshot and the billing week ends before the end date of // our search range, or the next snapshot will be in the next billing week. // Either way, we need to insert a snapshot to end the biling at the correct time. var endSnapshot = new MergedSnapshot( billingWeekFinalSnapshotTime, snapshot.CreatorChannels, snapshot.CreatorFreeAccessUsers, snapshot.SubscriberChannels, snapshot.Subscriber, snapshot.CalculatedAccountBalance); snapshots.Insert(i + 1, endSnapshot); } snapshot = snapshots[i]; } } } } } return(snapshots); }