using System.Globalization; using FoodLabeling.Application.Services.DbModels; using FoodLabeling.Domain.Shared.Helpers; using SqlSugar; namespace FoodLabeling.Application.Helpers; /// /// 打印成功后写入告警计时器(按 BatchId 幂等,一条批次一条计时器)。 /// public static class LabelAlertTimerWriteHelper { /// /// 根据打印批次创建告警计时器;已存在未删除记录或无有效过期时刻时直接返回。 /// public static async Task TryCreateFromPrintBatchAsync( ISqlSugarClient db, string batchId, string? createdBy, CancellationToken ct = default) { var bid = batchId?.Trim(); if (string.IsNullOrWhiteSpace(bid)) { return; } ct.ThrowIfCancellationRequested(); // BatchId 有唯一索引:含软删记录也不再插入,避免幂等补写撞唯一约束 var exists = await db.Queryable() .AnyAsync(x => x.BatchId == bid); if (exists) { return; } var task = (await db.Queryable() .Where(x => x.BatchId == bid) .OrderBy(x => x.CopyIndex) .Take(1) .ToListAsync(ct)) .FirstOrDefault(); if (task is null) { return; } var printedAt = task.PrintedAt ?? task.BaseTime ?? task.CreationTime; if (!ReportsPrintLogExpiryHelper.TryResolveExpiryDateTime( task.PrintInputJson, task.RenderTemplateJson, task.BaseTime, printedAt, out var expiresAt)) { return; } var label = (await db.Queryable() .Where(x => x.Id == task.LabelId) .Select(x => new { x.LabelName, x.LabelCode }) .Take(1) .ToListAsync(ct)) .FirstOrDefault(); var labelName = label?.LabelName?.Trim(); if (string.IsNullOrWhiteSpace(labelName)) { labelName = FoodLabelingDisplayConsts.NotAvailable; } string? productName = null; if (!string.IsNullOrWhiteSpace(task.ProductId)) { productName = (await db.Queryable() .Where(x => x.Id == task.ProductId && !x.IsDeleted) .Select(x => x.ProductName) .Take(1) .ToListAsync(ct)) .FirstOrDefault(); } var durationSeconds = Math.Max(0, (int)(expiresAt - printedAt).TotalSeconds); var title = BuildTitle(labelName, durationSeconds); var subtitle = BuildSubtitle(durationSeconds, expiresAt); var now = DateTime.Now; var entity = new FlLabelAlertTimerDbEntity { Id = YitIdHelper.NextId().ToString(), BatchId = bid, PrintTaskId = task.Id, LabelId = task.LabelId, LabelCode = label?.LabelCode?.Trim(), LabelName = labelName, ProductId = task.ProductId, ProductName = string.IsNullOrWhiteSpace(productName) ? null : productName.Trim(), LocationId = task.LocationId?.Trim() ?? string.Empty, PrintedAt = printedAt, BaseTime = task.BaseTime, ExpiresAt = expiresAt, DurationSeconds = durationSeconds, Title = title, Subtitle = subtitle, IsDeleted = false, DeletionTime = null, CreatedBy = createdBy, CreationTime = now }; await db.Insertable(entity).ExecuteCommandAsync(); } private static string BuildTitle(string labelName, int durationSeconds) { var hoursText = FormatDurationHoursLabel(durationSeconds); return string.IsNullOrWhiteSpace(hoursText) ? labelName : $"{labelName} ({hoursText})"; } private static string BuildSubtitle(int durationSeconds, DateTime expiresAt) { var hoursText = FormatDurationHoursLabel(durationSeconds); var timeText = expiresAt.ToString("h:mm tt", CultureInfo.CurrentCulture); return string.IsNullOrWhiteSpace(hoursText) ? $"Completes at {timeText}" : $"{hoursText} Completes at {timeText}"; } private static string FormatDurationHoursLabel(int durationSeconds) { if (durationSeconds <= 0) { return string.Empty; } var totalHours = durationSeconds / 3600.0; if (totalHours >= 1) { var rounded = (int)Math.Round(totalHours, MidpointRounding.AwayFromZero); rounded = Math.Max(1, rounded); return rounded == 1 ? "1 hour" : $"{rounded} hours"; } var minutes = Math.Max(1, (int)Math.Ceiling(durationSeconds / 60.0)); return minutes == 1 ? "1 minute" : $"{minutes} minutes"; } }