namespace Admin.NET.Plugin.AiDOP.Production; /// /// 产线工作日历(ShopCalendarWorkCtr) /// 路由前缀:/api/Production/shop-calendar/... /// [ApiDescriptionSettings(Order = 267, Description = "产线工作日历")] [Route("api/Production")] [AllowAnonymous] [NonUnify] public class ShopCalendarWorkCtrService : IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; private readonly UserManager _userManager; public ShopCalendarWorkCtrService(ISqlSugarClient db, UserManager userManager) { _db = db; _userManager = userManager; } [DisplayName("产线工作日历列表")] [HttpGet("shop-calendar/list")] public async Task GetList([FromQuery] ShopCalendarWorkCtrListInput input) { var pars = new List(); var where = "sc.IsActive = 1"; if (!string.IsNullOrWhiteSpace(input.ProdLine)) { where += " AND sc.ProdLine = @ProdLine"; pars.Add(new SugarParameter("@ProdLine", input.ProdLine.Trim())); } var orderClause = ResolveOrder(input.OrderBy, input.Sort); var offset = (input.Page - 1) * input.PageSize; var baseSql = $""" SELECT sc.ProdLine, sc.Ufld1 AS Descr, CASE sc.WeekDay WHEN 0 THEN '星期天' WHEN 1 THEN '星期一' WHEN 2 THEN '星期二' WHEN 3 THEN '星期三' WHEN 4 THEN '星期四' WHEN 5 THEN '星期五' WHEN 6 THEN '星期六' ELSE CAST(sc.WeekDay AS CHAR) END AS WeekDayName, REPLACE(CAST(CAST(sc.ShiftsStart1 AS DECIMAL(13,2)) AS CHAR), '.', ':') AS ShiftsStart1, sc.ShiftsHours1 AS ShiftsHours1, REPLACE(CAST(CAST(IFNULL(sc.ShiftsStart2,0) AS DECIMAL(13,2)) AS CHAR), '.', ':') AS ShiftsStart2, sc.ShiftsHours2 AS ShiftsHours2, sc.RecID AS Id, CASE WHEN sc.WeekDay = 0 THEN 7 ELSE sc.WeekDay END AS number, sc.WeekDay AS WeekDay, sc.`Domain` AS Domain, sc.Site AS Site, sc.WorkCtr AS WorkCtr FROM ShopCalendarWorkCtr sc WHERE {where} """; var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({baseSql}) AS t", pars); var list = await _db.Ado.SqlQueryAsync( $"SELECT * FROM ({baseSql}) AS t ORDER BY {orderClause} LIMIT {input.PageSize} OFFSET {offset}", pars); return new { total, page = input.Page, pageSize = input.PageSize, list }; } [DisplayName("生产线下拉(LineMaster)")] [HttpGet("shop-calendar/lines")] public async Task GetLines([FromQuery] string? domain) { var pars = new List(); var where = "IsActive = 1"; if (!string.IsNullOrWhiteSpace(domain)) { where += " AND `Domain` = @Domain"; pars.Add(new SugarParameter("@Domain", domain.Trim())); } var rows = await _db.Ado.SqlQueryAsync( $"SELECT DISTINCT `Line` AS Value, `Line` AS Label FROM LineMaster WHERE {where} ORDER BY `Line`", pars); return new { list = rows }; } [DisplayName("产线工作日历详情")] [HttpGet("shop-calendar/{id:long}")] public async Task GetDetail(long id) { var row = (await _db.Ado.SqlQueryAsync( """ SELECT RecID AS Id, ProdLine, WeekDay, ShiftsStart1, ShiftsHours1, ShiftsStart2, ShiftsHours2, ShiftsStart3, ShiftsHours3, ShiftsStart4, ShiftsHours4, Ufld1 AS Descr, `Domain`, Site, WorkCtr, TeamType, AllowOverTime, IsWorkDay FROM ShopCalendarWorkCtr WHERE RecID = @Id """, new List { new("@Id", id) })).FirstOrDefault() ?? throw Oops.Oh("记录不存在"); row.ShiftsStart1Text = DecimalHoursToTimeString(row.ShiftsStart1); row.ShiftsStart2Text = DecimalHoursToTimeString(row.ShiftsStart2); return row; } /// 保存 POST /api/Production/shop-calendar/save [DisplayName("保存产线工作日历")] [HttpPost("shop-calendar/save")] public async Task Save([FromBody] ShopCalendarWorkCtrSaveInput input) { var account = Truncate(_userManager.Account ?? "system", 8); var now = DateTime.Now; var domain = Truncate(input.Domain.Trim(), 8); var site = Truncate(input.Site.Trim(), 8); var prodLine = Truncate(input.ProdLine.Trim(), 8); var workCtr = string.IsNullOrWhiteSpace(input.WorkCtr) ? null : Truncate(input.WorkCtr.Trim(), 8); var ufld1 = string.IsNullOrWhiteSpace(input.Ufld1) ? null : Truncate(input.Ufld1.Trim(), 8); var s1 = NormalizeShiftDecimal(ParseTimeToDecimal(input.ShiftsStart1)); var s2 = NormalizeShiftDecimal(ParseTimeToDecimal(input.ShiftsStart2)); var h1 = input.ShiftsHours1 ?? 0m; var h2 = input.ShiftsHours2 ?? 0m; if (input.Id is null or 0) { var dup = await _db.Ado.GetIntAsync( """ SELECT COUNT(*) FROM ShopCalendarWorkCtr WHERE IsActive = 1 AND ProdLine = @ProdLine AND WeekDay = @WeekDay """, new List { new("@ProdLine", prodLine), new("@WeekDay", input.WeekDay) }); if (dup > 0) throw Oops.Oh("该生产线与星期组合已存在"); await _db.Ado.ExecuteCommandAsync( """ INSERT INTO ShopCalendarWorkCtr ( `Domain`, `Site`, `ProdLine`, `WeekDay`, `WorkCtr`, `Ufld1`, ShiftsStart1, ShiftsHours1, ShiftsStart2, ShiftsHours2, ShiftsStart3, ShiftsHours3, ShiftsStart4, ShiftsHours4, AllowOverTime, IsChanged, IsWorkDay, IsActive, IsConfirm, BusinessID, CreateUser, CreateTime, UpdateUser, UpdateTime ) VALUES ( @Domain, @Site, @ProdLine, @WeekDay, @WorkCtr, @Ufld1, @S1, @H1, @S2, @H2, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, @User, @Now, @User, @Now ) """, new List { new("@Domain", domain), new("@Site", site), new("@ProdLine", prodLine), new("@WeekDay", input.WeekDay), new("@WorkCtr", (object?)workCtr ?? DBNull.Value), new("@Ufld1", (object?)ufld1 ?? DBNull.Value), new("@S1", s1), new("@H1", h1), new("@S2", s2), new("@H2", h2), new("@User", account), new("@Now", now) }); var newId = await _db.Ado.GetIntAsync("SELECT LAST_INSERT_ID()"); return new { id = (long)newId, message = "新增成功" }; } var n = await _db.Ado.ExecuteCommandAsync( """ UPDATE ShopCalendarWorkCtr SET `Domain` = @Domain, `Site` = @Site, ProdLine = @ProdLine, WeekDay = @WeekDay, WorkCtr = @WorkCtr, Ufld1 = @Ufld1, ShiftsStart1 = @S1, ShiftsHours1 = @H1, ShiftsStart2 = @S2, ShiftsHours2 = @H2, UpdateUser = @User, UpdateTime = @Now WHERE RecID = @Id """, new List { new("@Id", input.Id!.Value), new("@Domain", domain), new("@Site", site), new("@ProdLine", prodLine), new("@WeekDay", input.WeekDay), new("@WorkCtr", (object?)workCtr ?? DBNull.Value), new("@Ufld1", (object?)ufld1 ?? DBNull.Value), new("@S1", s1), new("@H1", h1), new("@S2", s2), new("@H2", h2), new("@User", account), new("@Now", now) }); if (n == 0) throw Oops.Oh("记录不存在或未变更"); return new { id = input.Id, message = "保存成功" }; } [DisplayName("删除产线工作日历")] [HttpPost("shop-calendar/delete/{id:long}")] public async Task Delete(long id) { var n = await _db.Ado.ExecuteCommandAsync( "UPDATE ShopCalendarWorkCtr SET IsActive = 0, UpdateTime = @Now WHERE RecID = @Id", new List { new("@Id", id), new("@Now", DateTime.Now) }); if (n == 0) throw Oops.Oh("记录不存在"); return new { message = "已删除" }; } private static string Truncate(string s, int maxLen) { if (string.IsNullOrEmpty(s)) return s; return s.Length <= maxLen ? s : s[..maxLen]; } private static string ResolveOrder(string? orderBy, string? sort) { var desc = string.Equals(sort?.Trim(), "desc", StringComparison.OrdinalIgnoreCase); var dir = desc ? "DESC" : "ASC"; var key = orderBy?.Trim().ToLowerInvariant(); var col = key switch { "prodline" => "t.ProdLine", "weekdayname" => "t.number", _ => "t.number" }; return $"{col} {dir}, t.Id"; } private static decimal? ParseTimeToDecimal(string? s) { if (string.IsNullOrWhiteSpace(s)) return null; var p = s.Trim().Split(':'); if (p.Length < 2 || !int.TryParse(p[0], out var h) || !int.TryParse(p[1], out var m)) return null; return h + m / 60m; } private static decimal NormalizeShiftDecimal(decimal? value) { if (value is null) return 0m; // DB 列是 decimal(10,5),避免循环小数导致超精度写入报错。 return Math.Round(value.Value, 5, MidpointRounding.AwayFromZero); } private static string? DecimalHoursToTimeString(decimal? d) { if (d is null || d.Value == 0) return null; var total = (double)d.Value; var h = (int)Math.Floor(total); var m = (int)Math.Round((total - h) * 60); if (m >= 60) { h++; m = 0; } return $"{h:D2}:{m:D2}"; } private sealed class ShopCalendarListRow { public string? ProdLine { get; set; } public string? Descr { get; set; } public string? WeekDayName { get; set; } public string? ShiftsStart1 { get; set; } public decimal? ShiftsHours1 { get; set; } public string? ShiftsStart2 { get; set; } public decimal? ShiftsHours2 { get; set; } public long Id { get; set; } public int number { get; set; } public int WeekDay { get; set; } public string? Domain { get; set; } public string? Site { get; set; } public string? WorkCtr { get; set; } } private sealed class LineKvRow { public string? Value { get; set; } public string? Label { get; set; } } private sealed class ShopCalendarDetailRow { public long Id { get; set; } public string? ProdLine { get; set; } public int WeekDay { get; set; } public decimal? ShiftsStart1 { get; set; } public decimal? ShiftsHours1 { get; set; } public decimal? ShiftsStart2 { get; set; } public decimal? ShiftsHours2 { get; set; } public decimal? ShiftsStart3 { get; set; } public decimal? ShiftsHours3 { get; set; } public decimal? ShiftsStart4 { get; set; } public decimal? ShiftsHours4 { get; set; } public string? Descr { get; set; } public string? Domain { get; set; } public string? Site { get; set; } public string? WorkCtr { get; set; } public string? TeamType { get; set; } public decimal? AllowOverTime { get; set; } public bool? IsWorkDay { get; set; } public string? ShiftsStart1Text { get; set; } public string? ShiftsStart2Text { get; set; } } }