using Fake.DependencyInjection; using Fake.Application; using Fake.Domain.Exceptions; using Fake.Security.Claims; using Fake.Users; using Oms.Domain.CustomersAggregate.Repositories; using Oms.Domain.MarketingAggregate.Consts; using Oms.Domain.MarketingAggregate.Entities; using Oms.Domain.MarketingAggregate.Enums; using Oms.Domain.MarketingAggregate.Repositories; using Oms.Domain.OrdersAggregate.Repositories; using System.Text.Json; namespace Oms.Application.Shared; /// /// 积分发放、调账与等级升级。 /// public class MarketingPointsManager : IScopedDependency { private readonly IMemberPointsRuleRepository _ruleRepository; private readonly ICustomerPointsAccountRepository _accountRepository; private readonly ICustomerPointsFlowRepository _flowRepository; private readonly IMemberLevelRepository _levelRepository; private readonly ICustomerRepository _customerRepository; private readonly IOrderRepository _orderRepository; private readonly IMarketingAuditLogRepository _auditLogRepository; private readonly ICurrentUser _currentUser; public MarketingPointsManager( IMemberPointsRuleRepository ruleRepository, ICustomerPointsAccountRepository accountRepository, ICustomerPointsFlowRepository flowRepository, IMemberLevelRepository levelRepository, ICustomerRepository customerRepository, IOrderRepository orderRepository, IMarketingAuditLogRepository auditLogRepository, ICurrentUser currentUser) { _ruleRepository = ruleRepository; _accountRepository = accountRepository; _flowRepository = flowRepository; _levelRepository = levelRepository; _customerRepository = customerRepository; _orderRepository = orderRepository; _auditLogRepository = auditLogRepository; _currentUser = currentUser; } public async Task EarnOnOrderPaidAsync(int mainCustomerId, int orderId, decimal payableAmount, CancellationToken cancellationToken = default) { if (payableAmount <= 0) { return; } if (await _flowRepository.ExistsEarnForOrderAsync(mainCustomerId, orderId, cancellationToken)) { return; } var rule = await _ruleRepository.GetActiveRuleAsync(cancellationToken); if (rule == null) { return; } var points = (int)Math.Floor(payableAmount * rule.PointsPerYuan); if (points <= 0) { return; } var isFirstOrder = !await _orderRepository.AnyAsync( x => x.CustomerId == mainCustomerId && x.IsPaid > 0 && x.Id != orderId, cancellationToken); if (isFirstOrder && rule.FirstOrderMultiplier > 1) { points = (int)Math.Floor(points * rule.FirstOrderMultiplier); } if (rule.MaxPointsPerOrder > 0) { points = Math.Min(points, rule.MaxPointsPerOrder); } await ApplyPointsChangeAsync( mainCustomerId, PointsChangeType.Earn, points, MarketingPointsBizTypes.OrderPay, orderId, null, "订单支付积分", cancellationToken); await TryUpgradeLevelAsync(mainCustomerId, cancellationToken); } public async Task AdjustAsync(int mainCustomerId, int points, string remark, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(remark) || remark.Trim().Length < 10) { throw new BusinessException("调账原因至少 10 个字"); } await ApplyPointsChangeAsync( mainCustomerId, PointsChangeType.Adjust, points, MarketingPointsBizTypes.AdminAdjust, null, null, remark.Trim(), cancellationToken); await _auditLogRepository.InsertAsync(new MarketingAuditLog { Action = MarketingAuditActions.AdjustPoints, OperatorId = _currentUser.GetId(), TargetType = "customer", TargetId = mainCustomerId, AfterJson = JsonSerializer.Serialize(new { points }), Remark = remark.Trim() }, cancellationToken); if (points > 0) { await TryUpgradeLevelAsync(mainCustomerId, cancellationToken); } } public async Task GetOrCreateAccountAsync(int mainCustomerId, CancellationToken cancellationToken = default) { var account = await _accountRepository.GetByCustomerIdAsync(mainCustomerId, cancellationToken); if (account != null) { return account; } account = new CustomerPointsAccount { CustomerId = mainCustomerId, TotalPoints = 0, AvailablePoints = 0, Version = 0 }; await _accountRepository.InsertAsync(account, cancellationToken); return account; } private async Task ApplyPointsChangeAsync( int mainCustomerId, PointsChangeType changeType, int points, string bizType, int? bizId, string? bizNo, string? remark, CancellationToken cancellationToken) { if (points == 0) { return; } var account = await GetOrCreateAccountAsync(mainCustomerId, cancellationToken); if (changeType == PointsChangeType.Earn || changeType == PointsChangeType.Adjust) { if (points < 0 && account.AvailablePoints + points < 0) { throw new BusinessException("可用积分不足"); } account.TotalPoints = Math.Max(0, account.TotalPoints + Math.Max(0, points)); account.AvailablePoints += points; } else { account.AvailablePoints += points; } await _accountRepository.UpdateAsync(account, cancellationToken); await _flowRepository.InsertAsync(new CustomerPointsFlow { CustomerId = mainCustomerId, ChangeType = changeType, Points = points, BizType = bizType, BizId = bizId, BizNo = bizNo, Remark = remark }, cancellationToken); } private async Task TryUpgradeLevelAsync(int mainCustomerId, CancellationToken cancellationToken = default) { var account = await GetOrCreateAccountAsync(mainCustomerId, cancellationToken); var levels = await _levelRepository.GetAllEnabledOrderedAsync(cancellationToken); var target = levels .Where(x => x.MinPoints <= account.TotalPoints) .OrderByDescending(x => x.MinPoints) .ThenByDescending(x => x.Sort) .FirstOrDefault(); if (target == null) { return; } var customer = await _customerRepository.FirstOrDefaultAsync(x => x.Id == mainCustomerId, cancellationToken); if (customer == null || customer.MemberLevelId == target.Id) { return; } customer.MemberLevelId = target.Id; await _customerRepository.UpdateAsync(customer, cancellationToken); } }