using System.Collections.Concurrent;
using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Entity;
namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Idempotency;
///
/// 线程安全内存幂等存储——用于并发/单元测试。以 ConcurrentDictionary.TryAdd 模拟
/// (tenant_id, domain_code, fbillno) 的 DB UNIQUE 原子占位语义(TryAdd 原子,多并发仅一路成功)。
/// **不代表真实 MySQL 唯一约束**(那属 Phase 4B)。
///
public sealed class InMemoryIqcPostingStore : IIqcPostingStore
{
private readonly ConcurrentDictionary _store =
new(StringComparer.Ordinal);
private static string Key(long tenantId, string domainCode, string fbillNo)
=> $"{tenantId}|{domainCode ?? string.Empty}|{fbillNo ?? string.Empty}";
public Task TryInsertAsync(AdoIqcInventoryPosting posting)
{
if (posting == null) throw new ArgumentNullException(nameof(posting));
var key = Key(posting.TenantId, posting.DomainCode, posting.FbillNo);
var inserted = _store.TryAdd(key, posting);
return Task.FromResult(new PostingInsertResult
{
Inserted = inserted,
Existing = inserted ? null : _store.TryGetValue(key, out var e) ? e : null,
});
}
public Task GetAsync(long tenantId, string domainCode, string fbillNo)
{
_store.TryGetValue(Key(tenantId, domainCode, fbillNo), out var e);
return Task.FromResult(e);
}
public Task UpdateStatusAsync(AdoIqcInventoryPosting posting, string status)
{
if (posting == null) throw new ArgumentNullException(nameof(posting));
if (_store.TryGetValue(Key(posting.TenantId, posting.DomainCode, posting.FbillNo), out var e))
{
lock (e) { e.PostingStatus = status; e.UpdateTime = DateTime.Now; }
}
posting.PostingStatus = status;
return Task.CompletedTask;
}
/// 原子抢占:对既有记录取锁,校验 PROCESSING + stale 后刷新心跳。lock(e) 保证并发仅一路成功。
public Task TryRecoverStaleAsync(long tenantId, string domainCode, string fbillNo, DateTime staleBefore, DateTime now)
{
if (!_store.TryGetValue(Key(tenantId, domainCode, fbillNo), out var e))
return Task.FromResult(false);
lock (e)
{
var status = (e.PostingStatus ?? string.Empty).Trim().ToUpperInvariant();
var heartbeat = e.UpdateTime ?? e.CreateTime;
if (status == "PROCESSING" && heartbeat < staleBefore)
{
e.UpdateTime = now; // 续租=抢占
return Task.FromResult(true);
}
return Task.FromResult(false);
}
}
}