admin/src/Oms.Application/Shared/CustomerCodeGenerator.cs
zzh c36bdb237c 重构客户编号生成规则,并修复店铺订单 summary SQL 翻译错误。
采用年码+月码+序号编码(5/6位,单月最多10000);summary 接口改用 hasStoreFilter 避免 SqlSugar 生成非法 SQL。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 17:54:29 +08:00

192 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 Oms.Domain.CustomersAggregate.Repositories;
namespace Oms.Application.Shared;
/// <summary>
/// 客户编号生成:年码(A=2026)+月码(A=1月) + 序号编码。
/// 1~1000 为 5 位(如 AF0A1);1001~10000 为 6 位(如 AF10A1)。
/// </summary>
public static class CustomerCodeGenerator
{
private const int BaseYear = 2026;
private const int MaxSequence = 10000;
private const int MaxRetries = 5;
public static async Task<string> GenerateNextAsync(
ICustomerRepository repository,
CancellationToken cancellationToken = default)
{
for (var attempt = 0; attempt < MaxRetries; attempt++)
{
var code = await BuildNextCodeAsync(repository, cancellationToken);
if (await repository.GetByCustomerCodeAsync(code, cancellationToken) == null)
{
return code;
}
}
throw new BusinessException("客户编号生成失败,请重试");
}
internal static string BuildMonthPrefix(DateTime time) =>
$"{ToYearCode(time.Year)}{ToMonthCode(time.Month)}";
internal static string FormatSequence(int sequence, string monthPrefix)
{
if (sequence is < 1 or > MaxSequence)
{
throw new BusinessException($"客户序号超出范围(1~{MaxSequence})");
}
if (sequence <= 1000)
{
return monthPrefix + EncodeSuffix(sequence);
}
return EncodeExpanded(sequence, monthPrefix);
}
internal static bool TryParseSequence(string customerCode, string monthPrefix, out int sequence)
{
sequence = 0;
if (string.IsNullOrWhiteSpace(customerCode)
|| !customerCode.StartsWith(monthPrefix, StringComparison.Ordinal))
{
return false;
}
var body = customerCode[monthPrefix.Length..];
return body.Length switch
{
3 => TryDecodeSuffix(body, out sequence),
4 when char.IsDigit(body[0]) && body[0] != '0'
=> TryDecodeExpanded(body[0] - '0', body[1..], out sequence),
_ => false
};
}
private static async Task<string> BuildNextCodeAsync(
ICustomerRepository repository,
CancellationToken cancellationToken)
{
var now = DateTime.Now;
var monthPrefix = BuildMonthPrefix(now);
var codes = await repository.ListCustomerCodesByPrefixAsync(monthPrefix, cancellationToken);
var maxSeq = 0;
foreach (var code in codes)
{
if (TryParseSequence(code, monthPrefix, out var sequence) && sequence > maxSeq)
{
maxSeq = sequence;
}
}
var next = maxSeq + 1;
if (next > MaxSequence)
{
throw new BusinessException($"当月客户编号已达上限({monthPrefix},最多 {MaxSequence} 条)");
}
return FormatSequence(next, monthPrefix);
}
private static char ToYearCode(int year)
{
if (year < BaseYear)
{
throw new BusinessException($"客户编号暂不支持 {BaseYear} 年之前的年份");
}
var offset = year - BaseYear;
if (offset > 25)
{
throw new BusinessException($"客户编号年份编码超出范围({BaseYear}~{BaseYear + 25})");
}
return (char)('A' + offset);
}
private static char ToMonthCode(int month)
{
if (month is < 1 or > 12)
{
throw new BusinessException("月份无效");
}
return (char)('A' + month - 1);
}
private static string EncodeSuffix(int sequence)
{
if (sequence == 1000)
{
return "0A0";
}
var hundreds = sequence / 100;
var tens = (sequence / 10) % 10;
var ones = sequence % 10;
return $"{hundreds}{(char)('A' + tens)}{ones}";
}
private static bool TryDecodeSuffix(string suffix, out int sequence)
{
sequence = 0;
if (suffix.Length != 3
|| !char.IsDigit(suffix[0])
|| !char.IsDigit(suffix[2])
|| suffix[1] is < 'A' or > 'J')
{
return false;
}
if (suffix == "0A0")
{
sequence = 1000;
return true;
}
var hundreds = suffix[0] - '0';
var tens = suffix[1] - 'A';
var ones = suffix[2] - '0';
sequence = hundreds * 100 + tens * 10 + ones;
return sequence is >= 1 and <= 999;
}
private static string EncodeExpanded(int sequence, string monthPrefix)
{
var expansion = Math.Min(sequence / 1000, 9);
var local = sequence - expansion * 1000;
if (local == 0)
{
return monthPrefix + expansion + "0A0";
}
return monthPrefix + expansion + EncodeSuffix(local);
}
private static bool TryDecodeExpanded(int expansion, string suffix, out int sequence)
{
sequence = 0;
if (expansion is < 1 or > 9)
{
return false;
}
if (suffix == "0A0")
{
sequence = expansion == 9 ? 10000 : expansion * 1000;
return true;
}
if (!TryDecodeSuffix(suffix, out var local))
{
return false;
}
sequence = expansion * 1000 + local;
return sequence is >= 1001 and <= MaxSequence;
}
}