ShopCalendarWorkCtrService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. namespace Admin.NET.Plugin.AiDOP.Production;
  2. /// <summary>
  3. /// 产线工作日历(ShopCalendarWorkCtr)
  4. /// 路由前缀:/api/Production/shop-calendar/...
  5. /// </summary>
  6. [ApiDescriptionSettings(Order = 267, Description = "产线工作日历")]
  7. [Route("api/Production")]
  8. [AllowAnonymous]
  9. [NonUnify]
  10. public class ShopCalendarWorkCtrService : IDynamicApiController, ITransient
  11. {
  12. private readonly ISqlSugarClient _db;
  13. private readonly UserManager _userManager;
  14. public ShopCalendarWorkCtrService(ISqlSugarClient db, UserManager userManager)
  15. {
  16. _db = db;
  17. _userManager = userManager;
  18. }
  19. [DisplayName("产线工作日历列表")]
  20. [HttpGet("shop-calendar/list")]
  21. public async Task<object> GetList([FromQuery] ShopCalendarWorkCtrListInput input)
  22. {
  23. var tenantId = _userManager.TenantId;
  24. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  25. var where = "sc.IsActive = 1 AND sc.tenant_id = @TenantId";
  26. if (!string.IsNullOrWhiteSpace(input.ProdLine))
  27. {
  28. where += " AND sc.ProdLine = @ProdLine";
  29. pars.Add(new SugarParameter("@ProdLine", input.ProdLine.Trim()));
  30. }
  31. var orderClause = ResolveOrder(input.OrderBy, input.Sort);
  32. var offset = (input.Page - 1) * input.PageSize;
  33. var baseSql = $"""
  34. SELECT
  35. sc.ProdLine,
  36. sc.Ufld1 AS Descr,
  37. CASE sc.WeekDay
  38. WHEN 0 THEN '星期天'
  39. WHEN 1 THEN '星期一'
  40. WHEN 2 THEN '星期二'
  41. WHEN 3 THEN '星期三'
  42. WHEN 4 THEN '星期四'
  43. WHEN 5 THEN '星期五'
  44. WHEN 6 THEN '星期六'
  45. ELSE CAST(sc.WeekDay AS CHAR)
  46. END AS WeekDayName,
  47. REPLACE(CAST(CAST(sc.ShiftsStart1 AS DECIMAL(13,2)) AS CHAR), '.', ':') AS ShiftsStart1,
  48. sc.ShiftsHours1 AS ShiftsHours1,
  49. REPLACE(CAST(CAST(IFNULL(sc.ShiftsStart2,0) AS DECIMAL(13,2)) AS CHAR), '.', ':') AS ShiftsStart2,
  50. sc.ShiftsHours2 AS ShiftsHours2,
  51. sc.RecID AS Id,
  52. CASE WHEN sc.WeekDay = 0 THEN 7 ELSE sc.WeekDay END AS number,
  53. sc.WeekDay AS WeekDay,
  54. sc.`Domain` AS Domain,
  55. sc.Site AS Site,
  56. sc.WorkCtr AS WorkCtr
  57. FROM ShopCalendarWorkCtr sc
  58. WHERE {where}
  59. """;
  60. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({baseSql}) AS t", pars);
  61. var list = await _db.Ado.SqlQueryAsync<ShopCalendarListRow>(
  62. $"SELECT * FROM ({baseSql}) AS t ORDER BY {orderClause} LIMIT {input.PageSize} OFFSET {offset}", pars);
  63. return new { total, page = input.Page, pageSize = input.PageSize, list };
  64. }
  65. [DisplayName("生产线下拉(LineMaster)")]
  66. [HttpGet("shop-calendar/lines")]
  67. public async Task<object> GetLines([FromQuery] string? domain)
  68. {
  69. var tenantId = _userManager.TenantId;
  70. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  71. var where = "IsActive = 1 AND tenant_id = @TenantId";
  72. if (!string.IsNullOrWhiteSpace(domain))
  73. {
  74. where += " AND `Domain` = @Domain";
  75. pars.Add(new SugarParameter("@Domain", domain.Trim()));
  76. }
  77. var rows = await _db.Ado.SqlQueryAsync<LineKvRow>(
  78. $"SELECT DISTINCT `Line` AS Value, `Line` AS Label FROM LineMaster WHERE {where} ORDER BY `Line`", pars);
  79. return new { list = rows };
  80. }
  81. [DisplayName("产线工作日历详情")]
  82. [HttpGet("shop-calendar/{id:long}")]
  83. public async Task<object> GetDetail(long id)
  84. {
  85. var tenantId = _userManager.TenantId;
  86. var row = (await _db.Ado.SqlQueryAsync<ShopCalendarDetailRow>(
  87. """
  88. SELECT RecID AS Id, ProdLine, WeekDay, ShiftsStart1, ShiftsHours1, ShiftsStart2, ShiftsHours2,
  89. ShiftsStart3, ShiftsHours3, ShiftsStart4, ShiftsHours4,
  90. Ufld1 AS Descr, `Domain`, Site, WorkCtr, TeamType, AllowOverTime, IsWorkDay
  91. FROM ShopCalendarWorkCtr
  92. WHERE RecID = @Id AND tenant_id = @TenantId
  93. """,
  94. new List<SugarParameter> { new("@Id", id), new("@TenantId", tenantId) })).FirstOrDefault()
  95. ?? throw Oops.Oh("记录不存在");
  96. row.ShiftsStart1Text = DecimalHoursToTimeString(row.ShiftsStart1);
  97. row.ShiftsStart2Text = DecimalHoursToTimeString(row.ShiftsStart2);
  98. return row;
  99. }
  100. /// <summary>保存 POST /api/Production/shop-calendar/save</summary>
  101. [DisplayName("保存产线工作日历")]
  102. [HttpPost("shop-calendar/save")]
  103. public async Task<object> Save([FromBody] ShopCalendarWorkCtrSaveInput input)
  104. {
  105. var account = Truncate(_userManager.Account ?? "system", 8);
  106. var now = DateTime.Now;
  107. var domain = Truncate(input.Domain.Trim(), 8);
  108. var site = Truncate(input.Site.Trim(), 8);
  109. var prodLine = Truncate(input.ProdLine.Trim(), 8);
  110. var workCtr = string.IsNullOrWhiteSpace(input.WorkCtr) ? null : Truncate(input.WorkCtr.Trim(), 8);
  111. var ufld1 = string.IsNullOrWhiteSpace(input.Ufld1) ? null : Truncate(input.Ufld1.Trim(), 8);
  112. var s1 = NormalizeShiftDecimal(input.ShiftsStart1);
  113. var s2 = NormalizeShiftDecimal(input.ShiftsStart2);
  114. var h1 = input.ShiftsHours1 ?? 0m;
  115. var h2 = input.ShiftsHours2 ?? 0m;
  116. var tenantId = _userManager.TenantId;
  117. if (input.Id is null or 0)
  118. {
  119. var dup = await _db.Ado.GetIntAsync(
  120. """
  121. SELECT COUNT(*) FROM ShopCalendarWorkCtr
  122. WHERE IsActive = 1 AND ProdLine = @ProdLine AND WeekDay = @WeekDay AND tenant_id = @TenantId
  123. """,
  124. new List<SugarParameter>
  125. {
  126. new("@ProdLine", prodLine),
  127. new("@WeekDay", input.WeekDay),
  128. new("@TenantId", tenantId)
  129. });
  130. if (dup > 0)
  131. throw Oops.Oh("该生产线与星期组合已存在");
  132. await _db.Ado.ExecuteCommandAsync(
  133. """
  134. INSERT INTO ShopCalendarWorkCtr (
  135. `Domain`, `Site`, `ProdLine`, `WeekDay`, `WorkCtr`, `Ufld1`,
  136. ShiftsStart1, ShiftsHours1, ShiftsStart2, ShiftsHours2,
  137. ShiftsStart3, ShiftsHours3, ShiftsStart4, ShiftsHours4,
  138. AllowOverTime, IsChanged, IsWorkDay,
  139. IsActive, IsConfirm, BusinessID,
  140. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  141. ) VALUES (
  142. @Domain, @Site, @ProdLine, @WeekDay, @WorkCtr, @Ufld1,
  143. @S1, @H1, @S2, @H2,
  144. 0, 0, 0, 0,
  145. 0, 0, 1,
  146. 1, 0, 0,
  147. @User, @Now, @User, @Now, @TenantId
  148. )
  149. """,
  150. new List<SugarParameter>
  151. {
  152. new("@Domain", domain),
  153. new("@Site", site),
  154. new("@ProdLine", prodLine),
  155. new("@WeekDay", input.WeekDay),
  156. new("@WorkCtr", (object?)workCtr ?? DBNull.Value),
  157. new("@Ufld1", (object?)ufld1 ?? DBNull.Value),
  158. new("@S1", s1),
  159. new("@H1", h1),
  160. new("@S2", s2),
  161. new("@H2", h2),
  162. new("@User", account),
  163. new("@Now", now),
  164. new("@TenantId", tenantId)
  165. });
  166. var newId = await _db.Ado.GetIntAsync("SELECT LAST_INSERT_ID()");
  167. return new { id = (long)newId, message = "新增成功" };
  168. }
  169. var n = await _db.Ado.ExecuteCommandAsync(
  170. """
  171. UPDATE ShopCalendarWorkCtr
  172. SET `Domain` = @Domain,
  173. `Site` = @Site,
  174. ProdLine = @ProdLine,
  175. WeekDay = @WeekDay,
  176. WorkCtr = @WorkCtr,
  177. Ufld1 = @Ufld1,
  178. ShiftsStart1 = @S1,
  179. ShiftsHours1 = @H1,
  180. ShiftsStart2 = @S2,
  181. ShiftsHours2 = @H2,
  182. UpdateUser = @User,
  183. UpdateTime = @Now
  184. WHERE RecID = @Id AND tenant_id = @TenantId
  185. """,
  186. new List<SugarParameter>
  187. {
  188. new("@Id", input.Id!.Value),
  189. new("@Domain", domain),
  190. new("@Site", site),
  191. new("@ProdLine", prodLine),
  192. new("@WeekDay", input.WeekDay),
  193. new("@WorkCtr", (object?)workCtr ?? DBNull.Value),
  194. new("@Ufld1", (object?)ufld1 ?? DBNull.Value),
  195. new("@S1", s1),
  196. new("@H1", h1),
  197. new("@S2", s2),
  198. new("@H2", h2),
  199. new("@User", account),
  200. new("@Now", now),
  201. new("@TenantId", tenantId)
  202. });
  203. if (n == 0)
  204. throw Oops.Oh("记录不存在或未变更");
  205. return new { id = input.Id, message = "保存成功" };
  206. }
  207. [DisplayName("删除产线工作日历")]
  208. [HttpPost("shop-calendar/delete/{id:long}")]
  209. public async Task<object> Delete(long id)
  210. {
  211. var tenantId = _userManager.TenantId;
  212. var n = await _db.Ado.ExecuteCommandAsync(
  213. "UPDATE ShopCalendarWorkCtr SET IsActive = 0, UpdateTime = @Now WHERE RecID = @Id AND tenant_id = @TenantId",
  214. new List<SugarParameter> { new("@Id", id), new("@Now", DateTime.Now), new("@TenantId", tenantId) });
  215. if (n == 0)
  216. throw Oops.Oh("记录不存在");
  217. return new { message = "已删除" };
  218. }
  219. private static string Truncate(string s, int maxLen)
  220. {
  221. if (string.IsNullOrEmpty(s)) return s;
  222. return s.Length <= maxLen ? s : s[..maxLen];
  223. }
  224. private static string ResolveOrder(string? orderBy, string? sort)
  225. {
  226. var desc = string.Equals(sort?.Trim(), "desc", StringComparison.OrdinalIgnoreCase);
  227. var dir = desc ? "DESC" : "ASC";
  228. var key = orderBy?.Trim().ToLowerInvariant();
  229. var col = key switch
  230. {
  231. "prodline" => "t.ProdLine",
  232. "weekdayname" => "t.number",
  233. _ => "t.number"
  234. };
  235. return $"{col} {dir}, t.Id";
  236. }
  237. private static decimal? ParseTimeToDecimal(string? s)
  238. {
  239. if (string.IsNullOrWhiteSpace(s)) return null;
  240. var p = s.Trim().Split(':');
  241. if (p.Length < 2 || !int.TryParse(p[0], out var h) || !int.TryParse(p[1], out var m))
  242. return null;
  243. return h + m / 60m;
  244. }
  245. private static decimal NormalizeShiftDecimal(decimal? value)
  246. {
  247. if (value is null) return 0m;
  248. // DB 列是 decimal(10,5),避免循环小数导致超精度写入报错。
  249. return Math.Round(value.Value, 5, MidpointRounding.AwayFromZero);
  250. }
  251. private static string? DecimalHoursToTimeString(decimal? d)
  252. {
  253. if (d is null || d.Value == 0) return null;
  254. var total = (double)d.Value;
  255. var h = (int)Math.Floor(total);
  256. var m = (int)Math.Round((total - h) * 60);
  257. if (m >= 60) { h++; m = 0; }
  258. return $"{h:D2}:{m:D2}";
  259. }
  260. private sealed class ShopCalendarListRow
  261. {
  262. public string? ProdLine { get; set; }
  263. public string? Descr { get; set; }
  264. public string? WeekDayName { get; set; }
  265. public string? ShiftsStart1 { get; set; }
  266. public decimal? ShiftsHours1 { get; set; }
  267. public string? ShiftsStart2 { get; set; }
  268. public decimal? ShiftsHours2 { get; set; }
  269. public long Id { get; set; }
  270. public int number { get; set; }
  271. public int WeekDay { get; set; }
  272. public string? Domain { get; set; }
  273. public string? Site { get; set; }
  274. public string? WorkCtr { get; set; }
  275. }
  276. private sealed class LineKvRow
  277. {
  278. public string? Value { get; set; }
  279. public string? Label { get; set; }
  280. }
  281. private sealed class ShopCalendarDetailRow
  282. {
  283. public long Id { get; set; }
  284. public string? ProdLine { get; set; }
  285. public int WeekDay { get; set; }
  286. public decimal? ShiftsStart1 { get; set; }
  287. public decimal? ShiftsHours1 { get; set; }
  288. public decimal? ShiftsStart2 { get; set; }
  289. public decimal? ShiftsHours2 { get; set; }
  290. public decimal? ShiftsStart3 { get; set; }
  291. public decimal? ShiftsHours3 { get; set; }
  292. public decimal? ShiftsStart4 { get; set; }
  293. public decimal? ShiftsHours4 { get; set; }
  294. public string? Descr { get; set; }
  295. public string? Domain { get; set; }
  296. public string? Site { get; set; }
  297. public string? WorkCtr { get; set; }
  298. public string? TeamType { get; set; }
  299. public decimal? AllowOverTime { get; set; }
  300. public bool? IsWorkDay { get; set; }
  301. public string? ShiftsStart1Text { get; set; }
  302. public string? ShiftsStart2Text { get; set; }
  303. }
  304. }