Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

RewardPointService.cs

using Nop.Core;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Data;
using Nop.Services.Helpers;
using Nop.Services.Localization;

namespace Nop.Services.Orders;

/// 
/// Reward point service
/// 
public partial class RewardPointService : IRewardPointService
{
    #region Fields

    protected readonly IDateTimeHelper _dateTimeHelper;
    protected readonly ILocalizationService _localizationService;
    protected readonly IRepository _rewardPointsHistoryRepository;
    protected readonly RewardPointsSettings _rewardPointsSettings;

    #endregion

    #region Ctor

    public RewardPointService(IDateTimeHelper dateTimeHelper,
        ILocalizationService localizationService,
        IRepository rewardPointsHistoryRepository,
        RewardPointsSettings rewardPointsSettings)
    {
        _dateTimeHelper = dateTimeHelper;
        _localizationService = localizationService;
        _rewardPointsHistoryRepository = rewardPointsHistoryRepository;
        _rewardPointsSettings = rewardPointsSettings;
    }

    #endregion

    #region Utilities

    /// 
    /// Get query to load reward points history
    /// 
    /// Customer identifier; pass 0 to load all records
    /// Store identifier; pass null to load all records
    /// Whether to load reward points that did not yet activated
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the query to load reward points history
    /// 
    protected virtual async Task> GetRewardPointsQueryAsync(int customerId, int? storeId, bool showNotActivated = false)
    {
        var query = _rewardPointsHistoryRepository.Table;

        //filter by customer
        if (customerId > 0)
            query = query.Where(historyEntry => historyEntry.CustomerId == customerId);

        //filter by store
        if (!_rewardPointsSettings.PointsAccumulatedForAllStores && storeId > 0)
            query = query.Where(historyEntry => historyEntry.StoreId == storeId);

        //whether to show only the points that already activated
        if (!showNotActivated)
            query = query.Where(historyEntry => historyEntry.CreatedOnUtc < DateTime.UtcNow);

        //update points balance
        await UpdateRewardPointsBalanceAsync(query);

        return query;
    }

    /// 
    /// Update reward points balance if necessary
    /// 
    /// Input query
    /// A task that represents the asynchronous operation
    protected virtual async Task UpdateRewardPointsBalanceAsync(IQueryable query)
    {
        //get expired points
        var nowUtc = DateTime.UtcNow;
        var expiredPoints = query
            .Where(historyEntry => historyEntry.EndDateUtc < nowUtc && historyEntry.ValidPoints > 0)
            .OrderBy(historyEntry => historyEntry.CreatedOnUtc).ThenBy(historyEntry => historyEntry.Id).ToList();

        //reduce the balance for these points
        foreach (var historyEntry in expiredPoints)
        {
            await InsertRewardPointsHistoryEntryAsync(new RewardPointsHistory
            {
                CustomerId = historyEntry.CustomerId,
                StoreId = historyEntry.StoreId,
                Points = -historyEntry.ValidPoints.Value,
                Message = string.Format(await _localizationService.GetResourceAsync("RewardPoints.Expired"),
                    await _dateTimeHelper.ConvertToUserTimeAsync(historyEntry.CreatedOnUtc, DateTimeKind.Utc)),
                CreatedOnUtc = historyEntry.EndDateUtc.Value
            });

            historyEntry.ValidPoints = 0;
            await UpdateRewardPointsHistoryEntryAsync(historyEntry);
        }

        //get has not yet activated points, but it's time to do it
        var notActivatedPoints = query
            .Where(historyEntry => !historyEntry.PointsBalance.HasValue && historyEntry.CreatedOnUtc < nowUtc)
            .OrderBy(historyEntry => historyEntry.CreatedOnUtc).ThenBy(historyEntry => historyEntry.Id).ToList();
        if (!notActivatedPoints.Any())
            return;

        //get current points balance
        //LINQ to entities does not support Last method, thus order by desc and use First one
        var currentPointsBalance = query
            .OrderByDescending(historyEntry => historyEntry.CreatedOnUtc).ThenByDescending(historyEntry => historyEntry.Id)
            .FirstOrDefault(historyEntry => historyEntry.PointsBalance.HasValue)
            ?.PointsBalance ?? 0;

        //update appropriate records
        foreach (var historyEntry in notActivatedPoints)
        {
            currentPointsBalance += historyEntry.Points;
            historyEntry.PointsBalance = currentPointsBalance;
            await UpdateRewardPointsHistoryEntryAsync(historyEntry);
        }
    }

    /// 
    /// Insert the reward point history entry
    /// 
    /// Reward point history entry
    /// A task that represents the asynchronous operation
    protected virtual async Task InsertRewardPointsHistoryEntryAsync(RewardPointsHistory rewardPointsHistory)
    {
        await _rewardPointsHistoryRepository.InsertAsync(rewardPointsHistory);
    }

    #endregion

    #region Methods

    /// 
    /// Load reward point history records
    /// 
    /// Customer identifier; 0 to load all records
    /// Store identifier; pass null to load all records
    /// A value indicating whether to show reward points that did not yet activated
    /// Order Guid; pass null to load all record
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the reward point history records
    /// 
    public virtual async Task> GetRewardPointsHistoryAsync(int customerId = 0, int? storeId = null,
        bool showNotActivated = false, Guid? orderGuid = null, int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var query = await GetRewardPointsQueryAsync(customerId, storeId, showNotActivated);

        if (orderGuid.HasValue)
            query = query.Where(historyEntry => historyEntry.UsedWithOrder == orderGuid.Value);

        query = query.OrderByDescending(historyEntry => historyEntry.CreatedOnUtc)
            .ThenByDescending(historyEntry => historyEntry.Id);

        //return paged reward points history
        return await query.ToPagedListAsync(pageIndex, pageSize);
    }

    /// 
    /// Gets reward points balance
    /// 
    /// Customer identifier
    /// Store identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the balance
    /// 
    public virtual async Task GetRewardPointsBalanceAsync(int customerId, int storeId)
    {
        var query = (await GetRewardPointsQueryAsync(customerId, storeId))
            .OrderByDescending(historyEntry => historyEntry.CreatedOnUtc).ThenByDescending(historyEntry => historyEntry.Id);

        //return point balance of the first actual history entry
        return (await query.FirstOrDefaultAsync())?.PointsBalance ?? 0;
    }

    /// 
    /// Add reward points history record
    /// 
    /// Customer
    /// Number of points to add
    /// Store identifier
    /// Message
    /// The order for which points were redeemed (spent) as a payment
    /// Used amount
    /// Date and time of activating reward points; pass null to immediately activating
    /// Date and time when the reward points will no longer be valid; pass null to add date termless points
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the reward points history entry identifier
    /// 
    public virtual async Task AddRewardPointsHistoryEntryAsync(Customer customer, int points, int storeId, string message = "",
        Order usedWithOrder = null, decimal usedAmount = 0M, DateTime? activatingDate = null, DateTime? endDate = null)
    {
        ArgumentNullException.ThrowIfNull(customer);

        if (storeId == 0)
            throw new ArgumentException("Store ID should be valid");

        if (points < 0 && endDate.HasValue)
            throw new ArgumentException("End date is available only for positive points amount");

        //insert new history entry
        var newHistoryEntry = new RewardPointsHistory
        {
            CustomerId = customer.Id,
            StoreId = storeId,
            Points = points,
            PointsBalance = activatingDate.HasValue ? null : (int?)(await GetRewardPointsBalanceAsync(customer.Id, storeId) + points),
            UsedAmount = usedAmount,
            Message = message,
            CreatedOnUtc = activatingDate ?? DateTime.UtcNow,
            EndDateUtc = endDate,
            ValidPoints = points > 0 ? (int?)points : null,
            UsedWithOrder = usedWithOrder?.OrderGuid
        };
        await InsertRewardPointsHistoryEntryAsync(newHistoryEntry);

        //reduce valid points of previous entries
        if (points >= 0)
            return newHistoryEntry.Id;

        var withValidPoints = (await GetRewardPointsQueryAsync(customer.Id, storeId))
            .Where(historyEntry => historyEntry.ValidPoints > 0)
            .OrderBy(historyEntry => historyEntry.CreatedOnUtc).ThenBy(historyEntry => historyEntry.Id).ToList();
        foreach (var historyEntry in withValidPoints)
        {
            points += historyEntry.ValidPoints.Value;
            historyEntry.ValidPoints = Math.Max(points, 0);
            await UpdateRewardPointsHistoryEntryAsync(historyEntry);

            if (points >= 0)
                break;
        }

        return newHistoryEntry.Id;
    }

    /// 
    /// Gets a reward point history entry
    /// 
    /// Reward point history entry identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the reward point history entry
    /// 
    public virtual async Task GetRewardPointsHistoryEntryByIdAsync(int rewardPointsHistoryId)
    {
        return await _rewardPointsHistoryRepository.GetByIdAsync(rewardPointsHistoryId);
    }

    /// 
    /// Update the reward point history entry
    /// 
    /// Reward point history entry
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateRewardPointsHistoryEntryAsync(RewardPointsHistory rewardPointsHistory)
    {
        await _rewardPointsHistoryRepository.UpdateAsync(rewardPointsHistory);
    }

    /// 
    /// Delete the reward point history entry
    /// 
    /// Reward point history entry
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteRewardPointsHistoryEntryAsync(RewardPointsHistory rewardPointsHistory)
    {
        await _rewardPointsHistoryRepository.DeleteAsync(rewardPointsHistory);
    }

    #endregion
}