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