using Fake.Application;
using Oms.Domain.CustomersAggregate.Repositories;
namespace Oms.Application.Shared;
///
/// 客户编号生成:年码(A=2026)+月码(A=1月) + 序号编码。
/// 1~1000 为 5 位(如 AF0A1);1001~10000 为 6 位(如 AF10A1)。
///
public static class CustomerCodeGenerator
{
private const int BaseYear = 2026;
private const int MaxSequence = 10000;
private const int MaxRetries = 5;
public static async Task 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 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;
}
}