admin/src/Oms.Application/Shared/MarketingCustomerHelper.cs
zzh f972a28d79
All checks were successful
Build and Deploy / deploy (push) Successful in 3m50s
feat: 落地滚动会员、券发放与邀请分成奖励
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-06 17:40:24 +08:00

148 lines
5.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Fake.Application;
using Fake.Domain.Exceptions;
using Oms.Domain.CustomersAggregate.Entities;
using Oms.Domain.CustomersAggregate.Enums;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.MarketingAggregate.Entities;
using Oms.Domain.MarketingAggregate.Repositories;
using Oms.Domain.Shared.Enums;
namespace Oms.Application.Shared;
/// <summary>
/// 营销域主客户解析与价区键。
/// </summary>
public static class MarketingCustomerHelper
{
/// <summary>缺省/空价区键回落(卖价 JSON 标准键)。</summary>
public const string FallbackPriceTierKey = "普通客户";
/// <summary>新注册客户默认价区键(与会员等级名称对齐为「普通会员」)。</summary>
public const string DefaultNewCustomerPriceTierKey = "普通会员";
/// <summary>新注册客户默认会员等级编码/名称。</summary>
public const string DefaultMemberLevelName = "普通会员";
public static async Task<Customer> ResolveMarketingMainCustomerAsync(
ICustomerRepository customerRepository,
Customer sessionCustomer,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(sessionCustomer);
if (!sessionCustomer.ParentCustomerId.HasValue)
{
return sessionCustomer;
}
var mainCustomer = await customerRepository.FirstOrDefaultAsync(
x => x.Id == sessionCustomer.ParentCustomerId.Value,
cancellationToken);
if (mainCustomer == null)
{
throw new BusinessException("主客户不存在");
}
return mainCustomer;
}
public static async Task<int> ResolveMarketingMainCustomerIdAsync(
ICustomerRepository customerRepository,
Customer sessionCustomer,
CancellationToken cancellationToken = default)
{
var main = await ResolveMarketingMainCustomerAsync(customerRepository, sessionCustomer, cancellationToken);
return main.Id;
}
/// <summary>
/// 解析用于计价的价区键:主客户.PriceTierKey;空则回落「普通客户」。与会员等级无关。
/// </summary>
public static async Task<string?> ResolvePriceTierKeyAsync(
ICustomerRepository customerRepository,
IMemberLevelRepository memberLevelRepository,
int? customerId,
CancellationToken cancellationToken = default)
{
// memberLevelRepository 保留参数以兼容既有调用方签名;价区已与等级解耦。
_ = memberLevelRepository;
if (!customerId.HasValue || customerId.Value <= 0)
{
return FallbackPriceTierKey;
}
var sessionCustomer = await customerRepository.FirstOrDefaultAsync(x => x.Id == customerId.Value, cancellationToken);
if (sessionCustomer == null)
{
return FallbackPriceTierKey;
}
var mainCustomer = sessionCustomer.ParentCustomerId.HasValue
? await customerRepository.FirstOrDefaultAsync(x => x.Id == sessionCustomer.ParentCustomerId.Value, cancellationToken)
: sessionCustomer;
if (mainCustomer == null)
{
return FallbackPriceTierKey;
}
if (!string.IsNullOrWhiteSpace(mainCustomer.PriceTierKey))
{
return mainCustomer.PriceTierKey.Trim();
}
// 兼容未回填客户:短暂回退遗留 CustomerGroup,最终仍归一到标准键
return MapCustomerGroupToPriceTierKey(mainCustomer.CustomerGroup) ?? FallbackPriceTierKey;
}
public static string NormalizePriceTierKeyOrDefault(string? priceTierKey)
{
return string.IsNullOrWhiteSpace(priceTierKey) ? FallbackPriceTierKey : priceTierKey.Trim();
}
/// <summary>新客默认价区键。</summary>
public static string ResolveNewCustomerPriceTierKey(string? input) =>
string.IsNullOrWhiteSpace(input) ? DefaultNewCustomerPriceTierKey : input.Trim();
/// <summary>
/// 解析默认「普通会员」等级 Id;不存在则取启用等级中 MinPoints 最低者。
/// </summary>
public static async Task<int?> ResolveDefaultMemberLevelIdAsync(
IMemberLevelRepository memberLevelRepository,
CancellationToken cancellationToken = default)
{
var named = await memberLevelRepository.FirstOrDefaultAsync(
x => x.Status == EnableStatus.Enabled
&& (x.LevelCode == "V0"
|| x.LevelCode == "normal"
|| x.LevelName == DefaultMemberLevelName),
cancellationToken);
if (named != null)
{
return named.Id;
}
var levels = await memberLevelRepository.GetAllEnabledOrderedAsync(cancellationToken);
return levels.OrderBy(x => x.MinPoints).ThenBy(x => x.Sort).ThenBy(x => x.Id).FirstOrDefault()?.Id;
}
public static string? MapCustomerGroupToPriceTierKey(CustomerGroup? customerGroup)
{
if (!customerGroup.HasValue)
{
return FallbackPriceTierKey;
}
return customerGroup.Value switch
{
CustomerGroup.Normal => FallbackPriceTierKey,
CustomerGroup.Vip1 => "客户组1",
CustomerGroup.Vip2 => "客户组2",
CustomerGroup.Vip3 => "客户组3",
CustomerGroup.Vip4 => "客户组4",
CustomerGroup.Vip5 => "客户组5",
_ => FallbackPriceTierKey
};
}
}