LabelAlertTimerWriteHelper.cs 4.96 KB
using System.Globalization;
using FoodLabeling.Application.Services.DbModels;
using FoodLabeling.Domain.Shared.Helpers;
using SqlSugar;

namespace FoodLabeling.Application.Helpers;

/// <summary>
/// 打印成功后写入告警计时器(按 BatchId 幂等,一条批次一条计时器)。
/// </summary>
public static class LabelAlertTimerWriteHelper
{
    /// <summary>
    /// 根据打印批次创建告警计时器;已存在未删除记录或无有效过期时刻时直接返回。
    /// </summary>
    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<FlLabelAlertTimerDbEntity>()
            .AnyAsync(x => x.BatchId == bid);
        if (exists)
        {
            return;
        }

        var task = (await db.Queryable<FlLabelPrintTaskDbEntity>()
                .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<FlLabelDbEntity>()
                .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<FlProductDbEntity>()
                    .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";
    }
}