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; } }