admin/src/Oms.Application/Shared/MarketingOrderHelper.cs
zzh 89233bc3da feat: add customer marketing module (M1-M6 backend)
Implement referral, points, coupons, commission, member levels, and order integration with migration SQL and background jobs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 16:42:08 +08:00

101 lines
3.9 KiB
C#

using Fake.DependencyInjection;
using Oms.Application.Dtos.Marketing;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.MarketingAggregate.Entities;
using Oms.Domain.MarketingAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Entities;
namespace Oms.Application.Shared;
/// <summary>
/// 订单营销快照写入与查价叠加。
/// </summary>
public class MarketingOrderHelper : IScopedDependency
{
private readonly ICustomerRepository _customerRepository;
private readonly IMemberLevelRepository _memberLevelRepository;
private readonly MarketingCouponManager _couponManager;
public MarketingOrderHelper(
ICustomerRepository customerRepository,
IMemberLevelRepository memberLevelRepository,
MarketingCouponManager couponManager)
{
_customerRepository = customerRepository;
_memberLevelRepository = memberLevelRepository;
_couponManager = couponManager;
}
public async Task<MarketingPriceBreakdown> BuildBreakdownAsync(
int? customerId,
decimal receivableSubtotal,
int? couponInstanceId,
CancellationToken cancellationToken = default)
{
if (!customerId.HasValue || customerId.Value <= 0)
{
return MarketingBillingCalculator.Calculate(receivableSubtotal, 0M, null, null, null, null);
}
var sessionCustomer = await _customerRepository.FirstOrDefaultAsync(x => x.Id == customerId.Value, cancellationToken);
if (sessionCustomer == null)
{
return MarketingBillingCalculator.Calculate(receivableSubtotal, 0M, null, null, null, null);
}
var mainCustomer = await MarketingCustomerHelper.ResolveMarketingMainCustomerAsync(_customerRepository, sessionCustomer, cancellationToken);
var mainCustomerId = mainCustomer.Id;
MemberLevel? level = null;
if (mainCustomer.MemberLevelId.HasValue)
{
level = await _memberLevelRepository.FirstOrDefaultAsync(x => x.Id == mainCustomer.MemberLevelId.Value, cancellationToken);
}
var priceTierKey = level?.PriceTierKey
?? MarketingCustomerHelper.MapCustomerGroupToPriceTierKey(mainCustomer.CustomerGroup);
var couponInput = await _couponManager.ResolveCouponDiscountInputAsync(couponInstanceId, mainCustomerId, cancellationToken);
return MarketingBillingCalculator.Calculate(
receivableSubtotal,
level?.ExtraDiscountRate ?? 0M,
level?.Id,
level?.LevelName,
priceTierKey,
couponInput);
}
public void ApplySnapshotToOrder(Order order, MarketingPriceBreakdown breakdown, int? couponInstanceId)
{
order.MemberLevelId = breakdown.MemberLevelId;
order.PriceTierKey = breakdown.PriceTierKey;
order.ReceivableSubtotal = breakdown.ReceivableSubtotal;
order.LevelDiscountAmount = breakdown.LevelDiscountAmount;
order.CouponInstanceId = couponInstanceId ?? breakdown.CouponInstanceId;
order.CouponDiscountAmount = breakdown.CouponDiscountAmount;
order.PayableAmount = breakdown.PayableAmount;
}
public async Task<List<CouponAvailableOutput>> BuildAvailableCouponsAsync(
int? customerId,
decimal? amountAfterLevel,
CancellationToken cancellationToken = default)
{
if (!customerId.HasValue || customerId.Value <= 0)
{
return [];
}
var sessionCustomer = await _customerRepository.FirstOrDefaultAsync(x => x.Id == customerId.Value, cancellationToken);
if (sessionCustomer == null)
{
return [];
}
var mainCustomer = await MarketingCustomerHelper.ResolveMarketingMainCustomerAsync(
_customerRepository,
sessionCustomer,
cancellationToken);
return await _couponManager.BuildAvailableCouponsAsync(mainCustomer.Id, amountAfterLevel, cancellationToken);
}
}