| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Entity;
- using SqlSugar;
- using Yitter.IdGenerator;
- namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Idempotency;
- /// <summary>
- /// 生产 IQC 过账头幂等存储:依赖 `ado_iqc_inventory_posting` 的 DB UNIQUE `uk_ado_iqc_posting_fbillno`
- /// (tenant_id, domain_code, fbillno) 原子占位——**INSERT 直上,撞唯一约束(1062)时回读既有记录**,不做先查后插。
- ///
- /// ⚠️ Phase 4B(未在本轮实库验证):目标库 = aidopdev(ConfigId 1300000000001);DB UNIQUE 实际拒绝与真实并发行为待补。
- /// </summary>
- public sealed class SqlSugarIqcPostingStore : IIqcPostingStore, ITransient
- {
- private readonly ISqlSugarClient _db;
- public SqlSugarIqcPostingStore(ISqlSugarClient db)
- {
- _db = db;
- }
- public async Task<PostingInsertResult> TryInsertAsync(AdoIqcInventoryPosting posting)
- {
- if (posting == null) throw new ArgumentNullException(nameof(posting));
- if (posting.Id == 0) posting.Id = YitIdHelper.NextId();
- if (posting.CreateTime == default) posting.CreateTime = DateTime.Now;
- if (posting.UpdateTime == null) posting.UpdateTime = posting.CreateTime; // 认领即起租(lease 心跳基准)
- try
- {
- await _db.Insertable(posting).ExecuteCommandAsync();
- return new PostingInsertResult { Inserted = true };
- }
- catch (Exception ex) when (IsDuplicateKey(ex))
- {
- var existing = await GetAsync(posting.TenantId, posting.DomainCode, posting.FbillNo);
- return new PostingInsertResult { Inserted = false, Existing = existing };
- }
- }
- public async Task<AdoIqcInventoryPosting> GetAsync(long tenantId, string domainCode, string fbillNo)
- {
- var domain = domainCode ?? string.Empty;
- return await _db.Queryable<AdoIqcInventoryPosting>()
- .Where(x => x.TenantId == tenantId && x.DomainCode == domain && x.FbillNo == fbillNo)
- .FirstAsync();
- }
- public async Task UpdateStatusAsync(AdoIqcInventoryPosting posting, string status)
- {
- if (posting == null) throw new ArgumentNullException(nameof(posting));
- await _db.Ado.ExecuteCommandAsync(
- "UPDATE ado_iqc_inventory_posting SET posting_status=@st, update_time=@now WHERE tenant_id=@t AND domain_code=@d AND fbillno=@fb",
- new SugarParameter("@st", status), new SugarParameter("@now", DateTime.Now),
- new SugarParameter("@t", posting.TenantId), new SugarParameter("@d", posting.DomainCode ?? ""),
- new SugarParameter("@fb", posting.FbillNo));
- posting.PostingStatus = status;
- }
- public async Task<bool> TryRecoverStaleAsync(long tenantId, string domainCode, string fbillNo, DateTime staleBefore, DateTime now)
- {
- // 单条件原子更新:仅 PROCESSING 且心跳过期者被抢占(rows=1);并发多路仅一路命中,无先查后改窗口。
- var rows = await _db.Ado.ExecuteCommandAsync(
- "UPDATE ado_iqc_inventory_posting SET update_time=@now " +
- "WHERE tenant_id=@t AND domain_code=@d AND fbillno=@fb AND posting_status='PROCESSING' AND update_time < @stale",
- new SugarParameter("@now", now), new SugarParameter("@t", tenantId),
- new SugarParameter("@d", domainCode ?? ""), new SugarParameter("@fb", fbillNo),
- new SugarParameter("@stale", staleBefore));
- return rows == 1;
- }
- /// <summary>沿异常链识别 MySQL 唯一键冲突(1062 / Duplicate entry / 目标唯一索引名)。</summary>
- private static bool IsDuplicateKey(Exception ex)
- {
- for (var e = ex; e != null; e = e.InnerException)
- {
- var m = e.Message;
- if (!string.IsNullOrEmpty(m) &&
- (m.Contains("Duplicate entry") || m.Contains("1062") || m.Contains("uk_ado_iqc_posting_fbillno")))
- return true;
- }
- return false;
- }
- }
|