ReportsPrintLogExpiryHelper.cs 7.96 KB
using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace FoodLabeling.Application.Helpers;

/// <summary>
/// Print Log「Expiry Date」:从 <c>fl_label_print_task.PrintInputJson</c> 解析保质期展示文案。
/// 与 App 落库一致:多为整份模板快照(<c>elements[]</c>),到期日在 duration date 类元素的 <c>config.text</c>。
/// </summary>
public static class ReportsPrintLogExpiryHelper
{
    private static readonly string[] RootExpiryKeys =
    {
        "expiryDate",
        "expiry",
        "expirationDate",
        "bestBeforeDate",
        "useByDate"
    };

    private static readonly Regex ExpiryNamePattern = new(
        @"(durationdate|duration_date|duration\s*date|expirydate|expiry_date|expiry\s*date|expirationdate|expiration_date|usebydate|use_by)",
        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);

    private static readonly Regex ExcludeNamePattern = new(
        @"(currentdate|currenttime|prepped|prepdate|prepon|labelname)",
        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);

    /// <summary>
    /// 解析保质期展示文本;无法解析时返回「无」。
    /// </summary>
    public static string ExtractExpiryText(string? printInputJson)
    {
        if (string.IsNullOrWhiteSpace(printInputJson))
        {
            return "无";
        }

        try
        {
            using var doc = JsonDocument.Parse(printInputJson);
            if (doc.RootElement.ValueKind != JsonValueKind.Object)
            {
                return "无";
            }

            var root = doc.RootElement;

            if (TryGetRootExpiryText(root, out var fromRoot))
            {
                return fromRoot;
            }

            if (TryGetProperty(root, "elements", out var elements) && elements.ValueKind == JsonValueKind.Array)
            {
                if (TryExtractFromElements(elements, out var fromElements))
                {
                    return fromElements;
                }
            }
        }
        catch
        {
            return "无";
        }

        return "无";
    }

    private static bool TryGetRootExpiryText(JsonElement root, out string text)
    {
        text = "无";
        foreach (var prop in root.EnumerateObject())
        {
            var key = prop.Name.Trim();
            if (!RootExpiryKeys.Any(k => key.Equals(k, StringComparison.OrdinalIgnoreCase)))
            {
                continue;
            }

            if (TryFormatJsonValueAsExpiryText(prop.Value, out text))
            {
                return true;
            }
        }

        return false;
    }

    private static bool TryExtractFromElements(JsonElement elements, out string text)
    {
        text = "无";
        var bestScore = 0;
        string? bestText = null;
        var bestOrder = int.MinValue;

        foreach (var el in elements.EnumerateArray())
        {
            if (el.ValueKind != JsonValueKind.Object)
            {
                continue;
            }

            var score = ScoreExpiryElement(el);
            if (score <= 0)
            {
                continue;
            }

            var display = GetElementConfigDisplayText(el);
            if (string.IsNullOrWhiteSpace(display))
            {
                continue;
            }

            var order = TryGetIntProperty(el, "orderNum", "OrderNum") ?? 0;
            if (score > bestScore || (score == bestScore && order > bestOrder))
            {
                bestScore = score;
                bestOrder = order;
                bestText = display.Trim();
            }
        }

        if (string.IsNullOrWhiteSpace(bestText))
        {
            return false;
        }

        text = bestText;
        return true;
    }

    private static int ScoreExpiryElement(JsonElement el)
    {
        var nameHint = $"{GetStringProperty(el, "elementName", "ElementName")} {GetStringProperty(el, "inputKey", "InputKey")}"
            .Trim();
        if (string.IsNullOrWhiteSpace(nameHint))
        {
            nameHint = GetStringProperty(el, "id", "Id");
        }

        if (!string.IsNullOrWhiteSpace(nameHint) && ExcludeNamePattern.IsMatch(nameHint))
        {
            return -1;
        }

        var typeAdd = GetStringProperty(el, "typeAdd", "TypeAdd")
            .Trim()
            .ToLowerInvariant()
            .Replace("  ", " ", StringComparison.Ordinal);

        if (ExpiryNamePattern.IsMatch(nameHint))
        {
            return 100;
        }

        if (typeAdd.Contains("duration date", StringComparison.Ordinal))
        {
            return 90;
        }

        if (TryGetProperty(el, "config", out var cfg) || TryGetProperty(el, "Config", out cfg))
        {
            var native = GetStringProperty(cfg, "nativeSourceType", "NativeSourceType")
                .Trim()
                .ToUpperInvariant();
            if (native == "DATE" && ExpiryNamePattern.IsMatch(nameHint))
            {
                return 85;
            }
        }

        var type = GetStringProperty(el, "type", "Type").Trim().ToUpperInvariant();
        if (type is "DATE" or "DURATION" or "TIME")
        {
            if (ExpiryNamePattern.IsMatch(nameHint))
            {
                return 75;
            }
        }

        return 0;
    }

    private static string? GetElementConfigDisplayText(JsonElement el)
    {
        if (!TryGetProperty(el, "config", out var cfg) && !TryGetProperty(el, "Config", out cfg))
        {
            return null;
        }

        foreach (var key in new[] { "text", "Text", "format", "Format" })
        {
            if (cfg.TryGetProperty(key, out var v) && v.ValueKind == JsonValueKind.String)
            {
                var s = v.GetString()?.Trim();
                if (!string.IsNullOrWhiteSpace(s))
                {
                    return s;
                }
            }
        }

        return null;
    }

    private static bool TryFormatJsonValueAsExpiryText(JsonElement v, out string text)
    {
        text = "无";
        if (v.ValueKind == JsonValueKind.String)
        {
            var s = v.GetString()?.Trim();
            if (string.IsNullOrWhiteSpace(s))
            {
                return false;
            }

            text = s;
            return true;
        }

        if (v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var unix))
        {
            text = DateTimeOffset.FromUnixTimeSeconds(unix).LocalDateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
            return true;
        }

        if (v.ValueKind is JsonValueKind.True or JsonValueKind.False or JsonValueKind.Number)
        {
            text = v.ToString();
            return true;
        }

        return false;
    }

    private static bool TryGetProperty(JsonElement obj, string name, out JsonElement value)
    {
        if (obj.TryGetProperty(name, out value))
        {
            return true;
        }

        if (name.Length > 0)
        {
            var pascal = char.ToUpperInvariant(name[0]) + name[1..];
            if (obj.TryGetProperty(pascal, out value))
            {
                return true;
            }
        }

        value = default;
        return false;
    }

    private static string GetStringProperty(JsonElement obj, params string[] names)
    {
        foreach (var name in names)
        {
            if (TryGetProperty(obj, name, out var v) && v.ValueKind == JsonValueKind.String)
            {
                return v.GetString() ?? string.Empty;
            }
        }

        return string.Empty;
    }

    private static int? TryGetIntProperty(JsonElement obj, params string[] names)
    {
        foreach (var name in names)
        {
            if (!TryGetProperty(obj, name, out var v))
            {
                continue;
            }

            if (v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n))
            {
                return n;
            }
        }

        return null;
    }
}