using System.Globalization; using FoodLabeling.Application.Services.DbModels; using SqlSugar; namespace FoodLabeling.Application.Helpers; /// /// Print Log「Label ID」:按门店 + 自然日对打印任务排序,格式 yyyyMMdd-n(非 fl_label.LabelCode)。 /// public static class ReportsPrintLogDailyLabelIdHelper { /// /// 为列表/导出行计算门店当日打印序号。 /// public static async Task> ResolveDailyLabelIdsAsync( ISqlSugarClient db, IReadOnlyList tasks) { var result = new Dictionary(StringComparer.Ordinal); if (tasks.Count == 0) { return result; } foreach (var task in tasks) { if (string.IsNullOrWhiteSpace(task.TaskId)) { continue; } result.TryAdd(task.TaskId.Trim(), "无"); } var buckets = tasks .Where(t => !string.IsNullOrWhiteSpace(t.TaskId) && !string.IsNullOrWhiteSpace(t.LocationId) && t.PrintedAt != default) .GroupBy(t => (LocationId: t.LocationId!.Trim(), Day: t.PrintedAt.Date)) .ToList(); foreach (var bucket in buckets) { var locId = bucket.Key.LocationId; var dayStart = bucket.Key.Day; var dayEnd = dayStart.AddDays(1); var dayTasks = await db.Queryable() .Where(t => t.LocationId == locId) .Where(t => SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= dayStart && SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < dayEnd) .OrderBy(t => SqlFunc.IsNull(t.PrintedAt, t.CreationTime), OrderByType.Asc) .OrderBy(t => t.Id, OrderByType.Asc) .Select(t => new { t.Id, PrintedAt = SqlFunc.IsNull(t.PrintedAt, t.CreationTime) }) .ToListAsync(); for (var i = 0; i < dayTasks.Count; i++) { result[dayTasks[i].Id] = Format(dayStart, i + 1); } } return result; } /// /// 预览/即将打印:取门店在指定自然日的下一个打印序号(不落库)。 /// public static async Task ResolveNextDailyLabelIdAsync( ISqlSugarClient db, string locationId, DateTime referenceTime) { var locId = locationId?.Trim(); if (string.IsNullOrWhiteSpace(locId)) { return "无"; } var dayStart = referenceTime.Date; var dayEnd = dayStart.AddDays(1); var count = await db.Queryable() .Where(t => t.LocationId == locId) .Where(t => SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= dayStart && SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < dayEnd) .CountAsync(); return Format(dayStart, count + 1); } public static string Format(DateTime printDay, int sequence) => $"{printDay:yyyyMMdd}-{sequence}"; /// /// 为门店当日后续打印任务预留连续序号(不含已存在任务之外的并发锁,与列表展示规则一致)。 /// public static async Task> ResolveNextDailyLabelIdsAsync( ISqlSugarClient db, string locationId, DateTime day, int count) { var result = new List(); if (count <= 0 || string.IsNullOrWhiteSpace(locationId)) { return result; } var locId = locationId.Trim(); var dayStart = day.Date; var dayEnd = dayStart.AddDays(1); var existingCount = await db.Queryable() .Where(t => t.LocationId == locId) .Where(t => SqlFunc.IsNull(t.PrintedAt, t.CreationTime) >= dayStart && SqlFunc.IsNull(t.PrintedAt, t.CreationTime) < dayEnd) .CountAsync(); for (var i = 1; i <= count; i++) { result.Add(Format(dayStart, existingCount + i)); } return result; } /// /// App Print Log 单日筛选:解析 / 为服务器本地自然日。 /// 两者均未传时返回 null(不按日过滤,兼容 App 未传 printDate 的历史行为)。 /// public static DateTime? ResolvePrintLogFilterCalendarDay(DateTime? printDate, string? printDateDay) { var text = printDateDay?.Trim(); if (!string.IsNullOrWhiteSpace(text)) { if (DateTime.TryParseExact( text, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedExact)) { return parsedExact.Date; } if (DateTime.TryParse( text, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var parsed)) { return parsed.Date; } throw new Volo.Abp.UserFriendlyException("printDate 格式不正确,请使用 yyyy-MM-dd"); } if (!printDate.HasValue) { return null; } var value = printDate.Value; return value.Kind == DateTimeKind.Utc ? value.ToLocalTime().Date : value.Date; } /// /// 按自然日过滤打印任务(MySQL DATE(IFNULL(PrintedAt, CreationTime)),避免 DateTime 区间比较时区偏差)。 /// public static ISugarQueryable ApplyPrintTaskCalendarDayFilter( ISugarQueryable query, DateTime filterDay) { var dayText = filterDay.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); return query.Where("DATE(IFNULL(PrintedAt, CreationTime)) = @filterDay", new { filterDay = dayText }); } /// /// 多表 Join 查询按自然日过滤(首表别名 t)。 /// public static ISugarQueryable ApplyPrintTaskCalendarDayFilter( ISugarQueryable query, DateTime filterDay) { var dayText = filterDay.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); return query.Where("DATE(IFNULL(t.PrintedAt, t.CreationTime)) = @filterDay", new { filterDay = dayText }); } /// /// 五表 Join(首表别名 t)。 /// public static ISugarQueryable ApplyPrintTaskCalendarDayFilter( ISugarQueryable query, DateTime filterDay) { var dayText = filterDay.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); return query.Where("DATE(IFNULL(t.PrintedAt, t.CreationTime)) = @filterDay", new { filterDay = dayText }); } /// /// App Print Log 单日筛选:printDate 的自然日区间;未传则默认服务器当天。 /// public static (DateTime dayStart, DateTime dayEndExcl) ResolvePrintLogFilterDayRange(DateTime? printDate) { var day = ResolvePrintLogFilterCalendarDay(printDate, null) ?? DateTime.Today; return (day, day.AddDays(1)); } public readonly struct PrintTaskScopeKey { public PrintTaskScopeKey(string taskId, string? locationId, DateTime printedAt) { TaskId = taskId; LocationId = locationId; PrintedAt = printedAt; } public string TaskId { get; } public string? LocationId { get; } public DateTime PrintedAt { get; } } }