Mes165StockReader.cs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. using Admin.NET.Plugin.AiDOP.DataPlatform;
  2. using Microsoft.Extensions.Logging;
  3. namespace Admin.NET.Plugin.AiDOP.Order;
  4. /// <summary>
  5. /// 从 165(DOPDEMORQ_SQLSERVER)批量读取实时可用库存。
  6. ///
  7. /// 只读、只查 InvMaster;连接经 MdpSourceScopeFactory 走 mdp_source 配置,不新开连接串。
  8. /// 口径与本库旧实现逐字对齐(跨全部 Location 求和、COALESCE(AvailStatusQty, QtyOnHand, 0)、
  9. /// 不过滤 IsActive),本包只换数据新鲜度,不动口径。
  10. /// 分块 IN 查询,避免逐物料跨库往返。
  11. /// </summary>
  12. public sealed class Mes165StockReader : ITransient
  13. {
  14. private const string SourceCode = "DOPDEMORQ_SQLSERVER";
  15. private readonly MdpSourceScopeFactory _scopeFactory;
  16. private readonly ILogger _logger;
  17. public Mes165StockReader(MdpSourceScopeFactory scopeFactory, ILoggerFactory loggerFactory)
  18. {
  19. _scopeFactory = scopeFactory;
  20. _logger = loggerFactory.CreateLogger(nameof(Mes165StockReader));
  21. }
  22. /// <summary>按物料号批量取可用库存;返回不含未命中的物料(调用方按 0 处理)。</summary>
  23. public async Task<Dictionary<string, decimal>> LoadAvailableQtyAsync(
  24. IReadOnlyList<string> itemNumbers,
  25. string domain,
  26. int batchSize = 500,
  27. CancellationToken ct = default)
  28. {
  29. var result = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase);
  30. if (itemNumbers.Count == 0)
  31. return result;
  32. var remote = await _scopeFactory.GetScopeAsync(SourceCode, ct);
  33. var size = batchSize <= 0 ? 500 : batchSize;
  34. for (var offset = 0; offset < itemNumbers.Count; offset += size)
  35. {
  36. ct.ThrowIfCancellationRequested();
  37. var chunk = itemNumbers.Skip(offset).Take(size).ToList();
  38. var names = new List<string>(chunk.Count);
  39. var pars = new List<SugarParameter> { new("@Domain", domain) };
  40. for (var i = 0; i < chunk.Count; i++)
  41. {
  42. names.Add($"@i{i}");
  43. pars.Add(new SugarParameter($"@i{i}", chunk[i]));
  44. }
  45. var sql =
  46. $"""
  47. SELECT ItemNum AS ItemNum,
  48. SUM(COALESCE(AvailStatusQty, QtyOnHand, 0)) AS Qty
  49. FROM InvMaster
  50. WHERE Domain = @Domain
  51. AND ItemNum IN ({string.Join(",", names)})
  52. GROUP BY ItemNum
  53. """;
  54. var rows = await remote.Ado.SqlQueryAsync<StockRow>(sql, pars);
  55. foreach (var r in rows)
  56. {
  57. if (string.IsNullOrWhiteSpace(r.ItemNum)) continue;
  58. result[r.ItemNum.Trim()] = r.Qty;
  59. }
  60. }
  61. _logger.LogInformation(
  62. "[Mes165StockReader] domain={Domain} requested={Requested} matched={Matched}",
  63. domain, itemNumbers.Count, result.Count);
  64. return result;
  65. }
  66. private sealed class StockRow
  67. {
  68. public string? ItemNum { get; set; }
  69. public decimal Qty { get; set; }
  70. }
  71. }