InventoryInboundAdminService.cs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. using Microsoft.AspNetCore.Mvc;
  2. using Microsoft.Extensions.Logging;
  3. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  4. /// <summary>库存冷链手工补偿入口(管理员)。不接受前端 tenantId/domain/entityCode。</summary>
  5. [ApiDescriptionSettings(Order = 310, Description = "S5库存冷链入站")]
  6. [Route("api/S5InventoryInbound")]
  7. [NonUnify]
  8. public sealed class InventoryInboundAdminService : IDynamicApiController, ITransient
  9. {
  10. private readonly InventoryMdpSyncService _sync;
  11. private readonly InventoryReconService _recon;
  12. private readonly ISqlSugarClient _db;
  13. private readonly ILogger _logger;
  14. public InventoryInboundAdminService(
  15. InventoryMdpSyncService sync,
  16. InventoryReconService recon,
  17. ISqlSugarClient db,
  18. ILoggerFactory loggerFactory)
  19. {
  20. _sync = sync;
  21. _recon = recon;
  22. _db = db;
  23. _logger = loggerFactory.CreateLogger(nameof(InventoryInboundAdminService));
  24. }
  25. [DisplayName("库存冷链首刷")]
  26. [HttpPost("bootstrap")]
  27. [ApiDescriptionSettings(Name = "S5InventoryBootstrap")]
  28. public async Task<InventorySyncResult> Bootstrap(CancellationToken ct)
  29. {
  30. _logger.LogInformation("[S5InventoryInbound] bootstrap requested");
  31. return await _sync.RunBootstrapAsync(ct);
  32. }
  33. [DisplayName("库存冷链跑一轮")]
  34. [HttpPost("run-once")]
  35. [ApiDescriptionSettings(Name = "S5InventoryRunOnce")]
  36. public async Task<InventorySyncResult> RunOnce(CancellationToken ct)
  37. {
  38. _logger.LogInformation("[S5InventoryInbound] run-once requested");
  39. return await _sync.RunIncrementalAsync(ct);
  40. }
  41. [DisplayName("库存冷链全量校准")]
  42. [HttpPost("reconcile")]
  43. [ApiDescriptionSettings(Name = "S5InventoryReconcile")]
  44. public async Task<InventorySyncResult> Reconcile(CancellationToken ct)
  45. {
  46. _logger.LogInformation("[S5InventoryInbound] reconcile requested");
  47. return await _sync.RunReconcileFullAsync(ct);
  48. }
  49. [DisplayName("库存流水 stg→std 补转换")]
  50. [HttpPost("transform-std")]
  51. [ApiDescriptionSettings(Name = "S5InventoryTransformStd")]
  52. public async Task<InventorySyncResult> TransformStd(CancellationToken ct)
  53. {
  54. _logger.LogInformation("[S5InventoryInbound] transform-std requested");
  55. return await _sync.TransformTransStdFromStgAsync(ct);
  56. }
  57. /// <summary>
  58. /// 库存余额 stg→std 补物化:复用已完整落地的贴源批次,不访问源库。
  59. /// UPSERT 语义,不删除既有标准层数据。
  60. /// </summary>
  61. [DisplayName("库存余额 stg→std 补物化")]
  62. [HttpPost("transform-inventory-std")]
  63. [ApiDescriptionSettings(Name = "S5InventoryTransformInventoryStd")]
  64. public async Task<InventorySyncResult> TransformInventoryStd([FromQuery] string batchId, CancellationToken ct)
  65. {
  66. _logger.LogInformation("[S5InventoryInbound] transform-inventory-std requested batch={Batch}", batchId);
  67. return await _sync.TransformInventoryStdFromStgAsync(batchId, ct);
  68. }
  69. /// <summary>
  70. /// 日终对账与 bootstrap / reconcile / transform 共享同一互斥闸门:
  71. /// 对账读 mdp_std_inventory 并写差异,跑批期间的中间态会产生假差异,必须排他。
  72. /// 锁在本入口而不是 InventoryReconService 内部,避免与 InventoryMdpSyncService 的自锁重入。
  73. /// </summary>
  74. [DisplayName("库存日终对账(手工)")]
  75. [HttpPost("daily-recon")]
  76. [ApiDescriptionSettings(Name = "S5InventoryDailyRecon")]
  77. public async Task<object> DailyRecon(CancellationToken ct)
  78. {
  79. _logger.LogInformation("[S5InventoryInbound] daily-recon requested");
  80. await using var guard = await InventoryInboundLockGuard.TryAcquireAsync(_db, _logger, cancellationToken: ct);
  81. if (!guard.Acquired)
  82. {
  83. _logger.LogWarning("[S5InventoryInbound] daily-recon 跳过,互斥锁占用 reason={Reason}", guard.BusyReason);
  84. return new { diffs = 0, skipped = true, message = "lock busy" };
  85. }
  86. var diffs = await _recon.RunDailyReconAsync(ct);
  87. return new { diffs, skipped = false, message = diffs == 0 ? "clean" : "diffs written" };
  88. }
  89. }