ResourceOvertimeService.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. namespace Admin.NET.Plugin.AiDOP.Production;
  2. /// <summary>
  3. /// 产线加班(ResourceOccupancyTime)
  4. /// 路由前缀:/api/Production/overtime/...
  5. ///
  6. /// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退(原实现:类级 <c>[AllowAnonymous]</c> + 宽松解析,
  8. /// 未认证方可携带 <c>?tenantId=&lt;任意租户&gt;</c> 读写该租户数据)。
  9. /// </summary>
  10. [ApiDescriptionSettings(Order = 270, Description = "产线加班")]
  11. [Route("api/Production")]
  12. [NonUnify]
  13. public class ResourceOvertimeService : IDynamicApiController, ITransient
  14. {
  15. private readonly ISqlSugarClient _db;
  16. private readonly UserManager _userManager;
  17. public ResourceOvertimeService(ISqlSugarClient db, UserManager userManager)
  18. {
  19. _db = db;
  20. _userManager = userManager;
  21. }
  22. [DisplayName("产线加班列表")]
  23. [HttpGet("overtime/list")]
  24. public async Task<object> GetList([FromQuery] ResourceOvertimeListInput input)
  25. {
  26. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  27. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  28. var where = "r.IsActive = 1 AND r.tenant_id = @TenantId";
  29. if (!string.IsNullOrWhiteSpace(input.Resource))
  30. {
  31. where += " AND r.Resource = @Resource";
  32. pars.Add(new SugarParameter("@Resource", input.Resource.Trim()));
  33. }
  34. if (!string.IsNullOrWhiteSpace(input.StartTimeFrom))
  35. {
  36. where += " AND r.StartTime >= @StartTimeFrom";
  37. pars.Add(new SugarParameter("@StartTimeFrom", input.StartTimeFrom.Trim()));
  38. }
  39. if (!string.IsNullOrWhiteSpace(input.EndTimeFrom))
  40. {
  41. where += " AND r.EndTime >= @EndTimeFrom";
  42. pars.Add(new SugarParameter("@EndTimeFrom", input.EndTimeFrom.Trim()));
  43. }
  44. if (!string.IsNullOrWhiteSpace(input.ResourceType))
  45. {
  46. where += " AND r.ResourceType LIKE @ResourceType";
  47. pars.Add(new SugarParameter("@ResourceType", $"%{input.ResourceType.Trim()}%"));
  48. }
  49. var orderClause = ResolveOrder(input.OrderBy, input.Sort);
  50. var offset = (input.Page - 1) * input.PageSize;
  51. var baseSql = $"""
  52. SELECT
  53. r.RecID AS Id,
  54. r.`Domain`,
  55. r.Site,
  56. r.BusinessID,
  57. r.ResourceType,
  58. r.Resource,
  59. r.StartTime,
  60. r.EndTime,
  61. r.Descr,
  62. r.IsRest,
  63. r.IsActive,
  64. r.IsConfirm,
  65. r.IsChanged,
  66. r.Ufld1,
  67. r.Ufld2,
  68. ROUND(CAST(IFNULL(r.Ufld1, 0) AS DECIMAL(20, 10)) * 60, 0) AS Time1,
  69. ROUND(CAST(IFNULL(r.Ufld2, 0) AS DECIMAL(20, 10)) * 60, 0) AS Time2,
  70. r.CreateUser,
  71. r.UpdateUser,
  72. r.CreateTime,
  73. r.UpdateTime
  74. FROM ResourceOccupancyTime r
  75. WHERE {where}
  76. """;
  77. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  78. var list = await _db.Ado.SqlQueryAsync<ResourceOvertimeListRow>(
  79. $"SELECT * FROM ({baseSql}) AS t ORDER BY {orderClause} LIMIT {input.PageSize} OFFSET {offset}", pars);
  80. return new { total, page = input.Page, pageSize = input.PageSize, list };
  81. }
  82. [DisplayName("产线加班详情")]
  83. [HttpGet("overtime/{id:long}")]
  84. public async Task<object> GetDetail(long id)
  85. {
  86. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  87. var row = (await _db.Ado.SqlQueryAsync<ResourceOvertimeDetailRow>(
  88. """
  89. SELECT RecID AS Id, `Domain`, Site, ResourceType, Resource, StartTime, EndTime, Descr,
  90. Ufld1, Ufld2,
  91. ROUND(CAST(IFNULL(Ufld1, 0) AS DECIMAL(20, 10)) * 60, 0) AS WorkMinutes,
  92. ROUND(CAST(IFNULL(Ufld2, 0) AS DECIMAL(20, 10)) * 60, 0) AS RestMinutes
  93. FROM ResourceOccupancyTime
  94. WHERE RecID = @Id AND tenant_id = @TenantId
  95. """,
  96. new List<SugarParameter> { new("@Id", id), new("@TenantId", tenantId) })).FirstOrDefault()
  97. ?? throw Oops.Oh("记录不存在");
  98. return row;
  99. }
  100. [DisplayName("保存产线加班")]
  101. [HttpPost("overtime/save")]
  102. public async Task<object> Save([FromBody] ResourceOvertimeSaveInput input)
  103. {
  104. var account = Truncate(_userManager.Account ?? "system", 8);
  105. var now = DateTime.Now;
  106. var domain = Truncate(input.Domain.Trim(), 8);
  107. var site = string.IsNullOrWhiteSpace(input.Site) ? null : Truncate(input.Site.Trim(), 8);
  108. var resource = Truncate(input.Resource.Trim(), 20);
  109. var resType = string.IsNullOrWhiteSpace(input.ResourceType) ? "" : Truncate(input.ResourceType.Trim(), 20);
  110. var descr = string.IsNullOrWhiteSpace(input.Descr) ? "-" : Truncate(input.Descr.Trim(), 60);
  111. var ufld1Hours = input.WorkMinutes.HasValue ? input.WorkMinutes.Value / 60m : (decimal?)null;
  112. var ufld2Hours = input.RestMinutes.HasValue ? input.RestMinutes.Value / 60m : (decimal?)null;
  113. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  114. if (input.Id is null or 0)
  115. {
  116. await _db.Ado.ExecuteCommandAsync(
  117. """
  118. INSERT INTO ResourceOccupancyTime (
  119. `Domain`, Site, BusinessID, ResourceType, Resource, StartTime, EndTime, Descr,
  120. IsRest, IsActive, IsConfirm, IsChanged, Ufld1, Ufld2,
  121. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  122. ) VALUES (
  123. @Domain, @Site, 0, @ResourceType, @Resource, @StartTime, @EndTime, @Descr,
  124. 0, 1, 0, 0, @Ufld1, @Ufld2,
  125. @User, @Now, @User, @Now, @TenantId
  126. )
  127. """,
  128. new List<SugarParameter>
  129. {
  130. new("@Domain", domain),
  131. new("@Site", (object?)site ?? DBNull.Value),
  132. new("@ResourceType", resType),
  133. new("@Resource", resource),
  134. new("@StartTime", (object?)input.StartTime ?? DBNull.Value),
  135. new("@EndTime", (object?)input.EndTime ?? DBNull.Value),
  136. new("@Descr", descr),
  137. new("@Ufld1", (object?)ufld1Hours ?? DBNull.Value),
  138. new("@Ufld2", (object?)ufld2Hours ?? DBNull.Value),
  139. new("@User", account),
  140. new("@Now", now),
  141. new("@TenantId", tenantId)
  142. });
  143. var newId = await _db.Ado.GetIntAsync("SELECT LAST_INSERT_ID()");
  144. return new { id = (long)newId, message = "新增成功" };
  145. }
  146. var n = await _db.Ado.ExecuteCommandAsync(
  147. """
  148. UPDATE ResourceOccupancyTime
  149. SET `Domain` = @Domain,
  150. Site = @Site,
  151. ResourceType = @ResourceType,
  152. Resource = @Resource,
  153. StartTime = @StartTime,
  154. EndTime = @EndTime,
  155. Descr = @Descr,
  156. Ufld1 = @Ufld1,
  157. Ufld2 = @Ufld2,
  158. UpdateUser = @User,
  159. UpdateTime = @Now
  160. WHERE RecID = @Id AND tenant_id = @TenantId
  161. """,
  162. new List<SugarParameter>
  163. {
  164. new("@Id", input.Id!.Value),
  165. new("@Domain", domain),
  166. new("@Site", (object?)site ?? DBNull.Value),
  167. new("@ResourceType", resType),
  168. new("@Resource", resource),
  169. new("@StartTime", (object?)input.StartTime ?? DBNull.Value),
  170. new("@EndTime", (object?)input.EndTime ?? DBNull.Value),
  171. new("@Descr", descr),
  172. new("@Ufld1", (object?)ufld1Hours ?? DBNull.Value),
  173. new("@Ufld2", (object?)ufld2Hours ?? DBNull.Value),
  174. new("@User", account),
  175. new("@Now", now),
  176. new("@TenantId", tenantId)
  177. });
  178. if (n == 0)
  179. throw Oops.Oh("记录不存在或未变更");
  180. return new { id = input.Id, message = "保存成功" };
  181. }
  182. [DisplayName("删除产线加班")]
  183. [HttpPost("overtime/delete/{id:long}")]
  184. public async Task<object> Delete(long id)
  185. {
  186. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  187. var n = await _db.Ado.ExecuteCommandAsync(
  188. "UPDATE ResourceOccupancyTime SET IsActive = 0, UpdateTime = @Now WHERE RecID = @Id AND tenant_id = @TenantId",
  189. new List<SugarParameter> { new("@Id", id), new("@Now", DateTime.Now), new("@TenantId", tenantId) });
  190. if (n == 0)
  191. throw Oops.Oh("记录不存在");
  192. return new { message = "已删除" };
  193. }
  194. [DisplayName("生产线列表")]
  195. [HttpGet("overtime/lines")]
  196. public async Task<object> GetLines([FromQuery] string? domain)
  197. {
  198. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  199. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  200. var where = "IsActive = 1 AND tenant_id = @TenantId";
  201. if (!string.IsNullOrWhiteSpace(domain))
  202. {
  203. where += " AND `Domain` = @Domain";
  204. pars.Add(new SugarParameter("@Domain", domain.Trim()));
  205. }
  206. var rows = await _db.Ado.SqlQueryAsync<LineKvOvertime>(
  207. $"SELECT `Line` AS Value, `Line` AS Label, `Domain` AS Extra FROM LineMaster WHERE {where} ORDER BY `Line`", pars);
  208. return new { list = rows };
  209. }
  210. private static string Truncate(string s, int maxLen) =>
  211. s.Length <= maxLen ? s : s[..maxLen];
  212. private static string ResolveOrder(string? orderBy, string? sort)
  213. {
  214. var desc = string.Equals(sort?.Trim(), "desc", StringComparison.OrdinalIgnoreCase);
  215. var dir = desc ? "DESC" : "ASC";
  216. var key = orderBy?.Trim().ToLowerInvariant();
  217. var col = key switch
  218. {
  219. "resource" => "t.Resource",
  220. "starttime" => "t.StartTime",
  221. "endtime" => "t.EndTime",
  222. "resourcetype" => "t.ResourceType",
  223. _ => "t.Id"
  224. };
  225. return $"{col} {dir}";
  226. }
  227. private sealed class ResourceOvertimeListRow
  228. {
  229. public long Id { get; set; }
  230. public string? Domain { get; set; }
  231. public string? Site { get; set; }
  232. public long BusinessID { get; set; }
  233. public string? ResourceType { get; set; }
  234. public string? Resource { get; set; }
  235. public DateTime? StartTime { get; set; }
  236. public DateTime? EndTime { get; set; }
  237. public string? Descr { get; set; }
  238. public bool IsRest { get; set; }
  239. public bool IsActive { get; set; }
  240. public bool IsConfirm { get; set; }
  241. public bool IsChanged { get; set; }
  242. public string? Ufld1 { get; set; }
  243. public string? Ufld2 { get; set; }
  244. public decimal? Time1 { get; set; }
  245. public decimal? Time2 { get; set; }
  246. public string? CreateUser { get; set; }
  247. public string? UpdateUser { get; set; }
  248. public DateTime? CreateTime { get; set; }
  249. public DateTime? UpdateTime { get; set; }
  250. }
  251. private sealed class ResourceOvertimeDetailRow
  252. {
  253. public long Id { get; set; }
  254. public string? Domain { get; set; }
  255. public string? Site { get; set; }
  256. public string? ResourceType { get; set; }
  257. public string? Resource { get; set; }
  258. public DateTime? StartTime { get; set; }
  259. public DateTime? EndTime { get; set; }
  260. public string? Descr { get; set; }
  261. public string? Ufld1 { get; set; }
  262. public string? Ufld2 { get; set; }
  263. public decimal? WorkMinutes { get; set; }
  264. public decimal? RestMinutes { get; set; }
  265. }
  266. private sealed class LineKvOvertime
  267. {
  268. public string? Value { get; set; }
  269. public string? Label { get; set; }
  270. public string? Extra { get; set; }
  271. }
  272. }