ScheduleExceptionService.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. using Yitter.IdGenerator;
  2. namespace Admin.NET.Plugin.AiDOP.Production;
  3. /// <summary>
  4. /// 排产异常记录服务 🏭
  5. /// 路由前缀:/api/Production/schedule-exception/...
  6. ///
  7. /// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
  8. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退(原实现:类级 <c>[AllowAnonymous]</c> + 宽松解析,
  9. /// 未认证方可携带 <c>?tenantId=&lt;任意租户&gt;</c> 读写该租户数据)。
  10. /// </summary>
  11. [ApiDescriptionSettings(Order = 267, Description = "排产异常记录")]
  12. [Route("api/Production")]
  13. [NonUnify]
  14. public class ScheduleExceptionService : IDynamicApiController, ITransient
  15. {
  16. private const string C = "utf8mb4_general_ci";
  17. private readonly ISqlSugarClient _db;
  18. private readonly SqlSugarRepository<ScheduleExceptionMaster> _rep;
  19. private readonly UserManager _userManager;
  20. public ScheduleExceptionService(ISqlSugarClient db, SqlSugarRepository<ScheduleExceptionMaster> rep, UserManager userManager)
  21. {
  22. _db = db;
  23. _rep = rep;
  24. _userManager = userManager;
  25. }
  26. // ══════════════════════════════════════════════════════════════
  27. // 列表 GET /api/Production/schedule-exception/list
  28. // ══════════════════════════════════════════════════════════════
  29. /// <summary>排产异常分页列表(ScheduleExceptionMaster + ItemMaster.Descr1)</summary>
  30. [DisplayName("排产异常记录列表")]
  31. [HttpGet("schedule-exception/list")]
  32. public async Task<object> GetList([FromQuery] ScheduleExceptionListInput input)
  33. {
  34. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  35. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  36. var inner = new List<string> { "sm.tenant_id = @TenantId" };
  37. if (!string.IsNullOrWhiteSpace(input.OptTime))
  38. {
  39. inner.Add($"(CAST(sm.OptTime AS CHAR(50)) COLLATE {C}) LIKE @OptTimeLike");
  40. pars.Add(new SugarParameter("@OptTimeLike", $"%{input.OptTime.Trim()}%"));
  41. }
  42. if (!string.IsNullOrWhiteSpace(input.WorkOrd))
  43. {
  44. inner.Add($"(sm.WorkOrd COLLATE {C}) LIKE @WorkOrd");
  45. pars.Add(new SugarParameter("@WorkOrd", $"%{input.WorkOrd.Trim()}%"));
  46. }
  47. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  48. {
  49. inner.Add($"(sm.ItemNum COLLATE {C}) = @ItemNumEq");
  50. pars.Add(new SugarParameter("@ItemNumEq", input.ItemNum.Trim()));
  51. }
  52. var baseSql = BuildListBaseSql(string.Join(" AND ", inner));
  53. var offset = (input.Page - 1) * input.PageSize;
  54. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  55. var list = await _db.Ado.SqlQueryAsync<ScheduleExceptionListRow>(
  56. $"SELECT * FROM ({baseSql}) AS t ORDER BY t.Id DESC LIMIT {input.PageSize} OFFSET {offset}", pars);
  57. return new { total, page = input.Page, pageSize = input.PageSize, list };
  58. }
  59. private static string BuildListBaseSql(string innerWhere)
  60. {
  61. const string coll = "utf8mb4_general_ci";
  62. return $"""
  63. SELECT
  64. sm.RecID AS Id,
  65. sm.`Domain` AS Domain,
  66. sm.OptTime AS OptTime,
  67. sm.WorkOrd AS WorkOrd,
  68. sm.ItemNum AS ItemNum,
  69. im.Descr1 AS Descr1,
  70. sm.Remark AS Remark,
  71. sm.Type AS Type
  72. FROM ScheduleExceptionMaster sm
  73. LEFT JOIN ItemMaster im
  74. ON (sm.`Domain` COLLATE {coll}) = (im.`Domain` COLLATE {coll})
  75. AND (sm.ItemNum COLLATE {coll}) = (im.ItemNum COLLATE {coll})
  76. AND im.tenant_id = @TenantId
  77. WHERE {innerWhere}
  78. """;
  79. }
  80. // ══════════════════════════════════════════════════════════════
  81. // 详情 GET /api/Production/schedule-exception/{id}
  82. // ══════════════════════════════════════════════════════════════
  83. /// <summary>排产异常详情</summary>
  84. [DisplayName("排产异常记录详情")]
  85. [HttpGet("schedule-exception/{id:long}")]
  86. public async Task<object> GetDetail(long id)
  87. {
  88. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  89. var e = await _rep.GetFirstAsync(u => u.RecID == id && u.TenantId == tenantId)
  90. ?? throw Oops.Oh("记录不存在");
  91. return new
  92. {
  93. id = e.RecID,
  94. domain = e.Domain,
  95. workOrd = e.WorkOrd,
  96. itemNum = e.ItemNum,
  97. remark = e.Remark,
  98. type = e.Type,
  99. optTime = e.OptTime,
  100. createTime = e.CreateTime
  101. };
  102. }
  103. // ══════════════════════════════════════════════════════════════
  104. // 保存 POST /api/Production/schedule-exception/save
  105. // ══════════════════════════════════════════════════════════════
  106. /// <summary>保存排产异常(新增或编辑)</summary>
  107. [DisplayName("保存排产异常记录")]
  108. [ApiDescriptionSettings(Name = "SaveScheduleException"), HttpPost("schedule-exception/save")]
  109. public async Task<object> Save([FromBody] ScheduleExceptionSaveInput input)
  110. {
  111. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  112. if (input.Id is null or 0)
  113. {
  114. var entity = new ScheduleExceptionMaster
  115. {
  116. RecID = YitIdHelper.NextId(),
  117. Domain = input.Domain.Trim(),
  118. WorkOrd = input.WorkOrd.Trim(),
  119. ItemNum = input.ItemNum.Trim(),
  120. Remark = input.Remark?.Trim(),
  121. Type = input.Type?.Trim(),
  122. OptTime = input.OptTime ?? DateTime.Now,
  123. CreateTime = input.CreateTime ?? DateTime.Now,
  124. TenantId = tenantId
  125. };
  126. await _rep.InsertAsync(entity);
  127. return new { id = entity.RecID, message = "新增成功" };
  128. }
  129. var exist = await _rep.GetFirstAsync(u => u.RecID == input.Id!.Value && u.TenantId == tenantId)
  130. ?? throw Oops.Oh("记录不存在");
  131. exist.Domain = input.Domain.Trim();
  132. exist.WorkOrd = input.WorkOrd.Trim();
  133. exist.ItemNum = input.ItemNum.Trim();
  134. exist.Remark = input.Remark?.Trim();
  135. exist.Type = input.Type?.Trim();
  136. if (input.OptTime.HasValue) exist.OptTime = input.OptTime;
  137. if (input.CreateTime.HasValue) exist.CreateTime = input.CreateTime;
  138. await _rep.UpdateAsync(exist);
  139. return new { id = exist.RecID, message = "编辑成功" };
  140. }
  141. // ══════════════════════════════════════════════════════════════
  142. // 删除 POST /api/Production/schedule-exception/delete/{id}
  143. // ══════════════════════════════════════════════════════════════
  144. /// <summary>删除排产异常</summary>
  145. [DisplayName("删除排产异常记录")]
  146. [HttpPost("schedule-exception/delete/{id:long}")]
  147. public async Task<object> Delete(long id)
  148. {
  149. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  150. var n = await _db.Ado.ExecuteCommandAsync(
  151. "DELETE FROM ScheduleExceptionMaster WHERE RecID = @Id AND tenant_id = @TenantId",
  152. new List<SugarParameter> { new("@Id", id), new("@TenantId", tenantId) });
  153. if (n == 0)
  154. throw Oops.Oh("记录不存在");
  155. return new { message = "删除成功" };
  156. }
  157. private sealed class ScheduleExceptionListRow
  158. {
  159. public long Id { get; set; }
  160. public string? Domain { get; set; }
  161. public DateTime? OptTime { get; set; }
  162. public string? WorkOrd { get; set; }
  163. public string? ItemNum { get; set; }
  164. public string? Descr1 { get; set; }
  165. public string? Remark { get; set; }
  166. public string? Type { get; set; }
  167. }
  168. }