namespace Admin.NET.Plugin.AiDOP.Supply; /// /// 编号规则服务。第一阶段兼容现有 NbrControl 规则,替代业务代码直接调用编号存储过程。 /// public class NumberRuleService : ITransient { private readonly ISqlSugarClient _db; public NumberRuleService(ISqlSugarClient db) { _db = db; } public async Task> NextBatchAsync(string ruleCode, string domain, int count, string operatorNo) { try { _db.Ado.BeginTran(); var numbers = await NextBatchInCurrentTransactionAsync(ruleCode, domain, count, operatorNo); _db.Ado.CommitTran(); return numbers; } catch { _db.Ado.RollbackTran(); throw; } } public async Task> NextBatchInCurrentTransactionAsync(string ruleCode, string domain, int count, string operatorNo) { if (string.IsNullOrWhiteSpace(ruleCode)) throw Oops.Oh("编号规则不能为空。"); if (count <= 0) throw Oops.Oh("编号数量必须大于 0。"); var normalizedRuleCode = ruleCode.Trim(); var normalizedDomain = domain?.Trim() ?? string.Empty; var now = DateTime.Now; var rule = (await _db.Ado.SqlQueryAsync( """ SELECT RecID AS Id, NbrType, NbrPre1, NbrPre2, NbrPre3, DateType, IsDateType, Description, IniValue, MinValue, `MaxValue` AS `MaxValue`, ResetValue, AllowReset, AllowSkip, AllowManual, IsActive, IsConfirm, Domain, tenant_id AS TenantId FROM NbrControl WHERE LOWER(TRIM(IFNULL(NbrType,''))) = LOWER(@RuleCode) AND ( IFNULL(TRIM(domain_code),'') = @Domain OR IFNULL(TRIM(Domain),'') = @Domain OR IFNULL(TRIM(domain_code),'') = '' OR IFNULL(TRIM(Domain),'') = '' ) AND IFNULL(IsActive, 1) = 1 ORDER BY CASE WHEN IFNULL(TRIM(domain_code),'') = @Domain THEN 0 WHEN IFNULL(TRIM(Domain),'') = @Domain THEN 1 ELSE 2 END, RecID LIMIT 1 FOR UPDATE """, new SugarParameter("@RuleCode", normalizedRuleCode), new SugarParameter("@Domain", normalizedDomain))).FirstOrDefault(); if (rule == null) throw Oops.Oh($"未找到编号规则:{normalizedRuleCode}"); var sequenceDate = NumberRuleFormatter.ResolveSequenceDate(rule.DateType, rule.IsDateType, now); var dayInfo = await GetDayInfoForUpdateAsync(rule, normalizedRuleCode, normalizedDomain, sequenceDate); var firstSerial = ResolveFirstSerial(rule, dayInfo); var lastSerial = firstSerial + count - 1; if (rule.MaxValue.HasValue && lastSerial > rule.MaxValue.Value) { if (rule.AllowReset == true) { firstSerial = (rule.ResetValue ?? 0) + 1; lastSerial = firstSerial + count - 1; } if (lastSerial > rule.MaxValue.Value) throw Oops.Oh($"编号规则 {normalizedRuleCode} 已超过最大值 {rule.MaxValue.Value}。"); } var numbers = Enumerable.Range(firstSerial, count) .Select(serial => NumberRuleFormatter.BuildNumber( rule.NbrPre1, rule.NbrPre2, rule.NbrPre3, rule.DateType, rule.IsDateType, serial, rule.MaxValue, now)) .ToList(); if (dayInfo == null) await InsertDayInfoAsync(rule, sequenceDate, lastSerial, operatorNo, now); else await UpdateDayInfoAsync(dayInfo.Id, lastSerial, operatorNo, now); return numbers; } private async Task GetDayInfoForUpdateAsync(NbrControlRuleRow rule, string ruleCode, string domain, DateTime? sequenceDate) { var dateFilter = sequenceDate.HasValue ? "AND DATE(Today) = DATE(@Today)" : string.Empty; return (await _db.Ado.SqlQueryAsync( $""" SELECT RecID AS Id, NextValue FROM NbrDayInfo WHERE LOWER(TRIM(IFNULL(NbrType,''))) = LOWER(@RuleCode) AND IFNULL(TRIM(Domain),'') = @Domain {dateFilter} ORDER BY RecID LIMIT 1 FOR UPDATE """, new SugarParameter("@RuleCode", rule.NbrType ?? ruleCode), new SugarParameter("@Domain", rule.Domain ?? domain), new SugarParameter("@Today", sequenceDate ?? DateTime.Today))).FirstOrDefault(); } private async Task InsertDayInfoAsync(NbrControlRuleRow rule, DateTime? sequenceDate, int nextValue, string operatorNo, DateTime now) { await _db.Ado.ExecuteCommandAsync( """ INSERT INTO NbrDayInfo (NbrType, Description, NbrPre1, NbrPre2, NbrPre3, Today, NextValue, IniValue, MinValue, `MaxValue`, ResetValue, AllowReset, AllowSkip, AllowManual, DateType, IsDateType, IsActive, IsConfirm, Domain, CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id) VALUES (@NbrType, @Description, @NbrPre1, @NbrPre2, @NbrPre3, @Today, @NextValue, @IniValue, @MinValue, @MaxValue, @ResetValue, @AllowReset, @AllowSkip, @AllowManual, @DateType, @IsDateType, @IsActive, @IsConfirm, @Domain, @UserNo, @Now, @UserNo, @Now, @TenantId) """, new SugarParameter("@NbrType", rule.NbrType ?? string.Empty), new SugarParameter("@Description", rule.Description ?? string.Empty), new SugarParameter("@NbrPre1", rule.NbrPre1 ?? string.Empty), new SugarParameter("@NbrPre2", rule.NbrPre2 ?? string.Empty), new SugarParameter("@NbrPre3", rule.NbrPre3 ?? string.Empty), new SugarParameter("@Today", sequenceDate ?? now.Date), new SugarParameter("@NextValue", nextValue), new SugarParameter("@IniValue", rule.IniValue), new SugarParameter("@MinValue", rule.MinValue), new SugarParameter("@MaxValue", rule.MaxValue), new SugarParameter("@ResetValue", rule.ResetValue), new SugarParameter("@AllowReset", rule.AllowReset), new SugarParameter("@AllowSkip", rule.AllowSkip), new SugarParameter("@AllowManual", rule.AllowManual), new SugarParameter("@DateType", rule.DateType ?? string.Empty), new SugarParameter("@IsDateType", rule.IsDateType), new SugarParameter("@IsActive", rule.IsActive ?? true), new SugarParameter("@IsConfirm", rule.IsConfirm ?? true), new SugarParameter("@Domain", rule.Domain ?? string.Empty), new SugarParameter("@UserNo", operatorNo ?? string.Empty), new SugarParameter("@Now", now), new SugarParameter("@TenantId", rule.TenantId)); } private async Task UpdateDayInfoAsync(int dayInfoId, int nextValue, string operatorNo, DateTime now) { await _db.Ado.ExecuteCommandAsync( """ UPDATE NbrDayInfo SET NextValue=@NextValue, UpdateUser=@UpdateUser, UpdateTime=@UpdateTime WHERE RecID=@Id """, new SugarParameter("@NextValue", nextValue), new SugarParameter("@UpdateUser", operatorNo ?? string.Empty), new SugarParameter("@UpdateTime", now), new SugarParameter("@Id", dayInfoId)); } private static int ResolveFirstSerial(NbrControlRuleRow rule, NbrDayInfoRow? dayInfo) { if (dayInfo?.NextValue != null) return dayInfo.NextValue.Value + 1; if (rule.IniValue.HasValue) return rule.IniValue.Value; if (rule.MinValue.HasValue) return rule.MinValue.Value; return 1; } private sealed class NbrControlRuleRow { public int Id { get; set; } public string? NbrType { get; set; } public string? NbrPre1 { get; set; } public string? NbrPre2 { get; set; } public string? NbrPre3 { get; set; } public string? DateType { get; set; } public bool? IsDateType { get; set; } public string? Description { get; set; } public int? IniValue { get; set; } public int? MinValue { get; set; } public int? MaxValue { get; set; } public int? ResetValue { get; set; } public bool? AllowReset { get; set; } public bool? AllowSkip { get; set; } public bool? AllowManual { get; set; } public bool? IsActive { get; set; } public bool? IsConfirm { get; set; } public string? Domain { get; set; } public long? TenantId { get; set; } } private sealed class NbrDayInfoRow { public int Id { get; set; } public int? NextValue { get; set; } } }