using System.Collections.Concurrent;
using Admin.NET.Plugin.AiDOP.Supply;
namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Numbering;
///
/// 进程内原子取号 provider(线程安全)——用于单元/并发测试与「同进程强唯一」证明。
/// 用 ConcurrentDictionary 计数器按 scope(ruleCode|domain|dateBucket) 原子预留连续区段,
/// 复用 NumberRuleFormatter 做格式化。**不代表跨实例/真实 MySQL 唯一性**(那属 Phase 4B)。
///
public sealed class InMemoryBusinessNumberProvider : IBusinessNumberProvider
{
private readonly Dictionary _rules;
private readonly Func _clock;
private readonly ConcurrentDictionary _counters = new();
public InMemoryBusinessNumberProvider(IEnumerable rules, Func clock = null)
{
_rules = (rules ?? throw new ArgumentNullException(nameof(rules)))
.ToDictionary(r => r.RuleCode, StringComparer.OrdinalIgnoreCase);
_clock = clock ?? (() => DateTime.Now);
}
public Task> NextBatchAsync(string ruleCode, string domain, int count, string operatorNo)
{
if (string.IsNullOrWhiteSpace(ruleCode)) throw new ArgumentException("ruleCode 不能为空", nameof(ruleCode));
if (count <= 0) throw new ArgumentException("count 必须大于 0", nameof(count));
if (!_rules.TryGetValue(ruleCode.Trim(), out var rule))
throw new InvalidOperationException($"未找到编号规则:{ruleCode}");
var now = _clock();
var sequenceDate = NumberRuleFormatter.ResolveSequenceDate(rule.DateType, rule.IsDateType, now);
var bucket = sequenceDate?.ToString("yyyyMMdd") ?? "-";
var key = $"{rule.RuleCode}|{domain ?? string.Empty}|{bucket}";
// 原子预留连续 count 个流水:AddOrUpdate 经 CAS 串行化,返回值即本次区段末值,无重叠。
var last = _counters.AddOrUpdate(key, rule.IniValue + count - 1, (_, v) => v + count);
var first = last - count + 1;
var numbers = Enumerable.Range(first, count)
.Select(serial => NumberRuleFormatter.BuildNumber(
rule.Prefix1, rule.Prefix2, rule.Prefix3,
rule.DateType, rule.IsDateType, serial, rule.MaxValue, now))
.ToList();
return Task.FromResult>(numbers);
}
}