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";
}
}