using System.Text.Json; using TakeoutSaaS.Application.App.Members.MessageReach.Dto; using TakeoutSaaS.Domain.Membership.Entities; using TakeoutSaaS.Domain.Membership.Enums; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; namespace TakeoutSaaS.Application.App.Members.MessageReach; internal static class MemberMessageReachMapping { internal static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) { PropertyNameCaseInsensitive = true }; internal static MemberMessageAudienceType ParseAudienceType(string? value) { return (value ?? string.Empty).Trim().ToLowerInvariant() switch { "all" => MemberMessageAudienceType.All, "tag" => MemberMessageAudienceType.Tags, "tags" => MemberMessageAudienceType.Tags, _ => throw new BusinessException(ErrorCodes.BadRequest, "audienceType 非法") }; } internal static string ToAudienceTypeText(MemberMessageAudienceType value) { return value switch { MemberMessageAudienceType.All => "all", MemberMessageAudienceType.Tags => "tag", _ => "all" }; } internal static MemberMessageScheduleType ParseScheduleType(string? value) { return (value ?? string.Empty).Trim().ToLowerInvariant() switch { "immediate" => MemberMessageScheduleType.Immediate, "scheduled" => MemberMessageScheduleType.Scheduled, _ => throw new BusinessException(ErrorCodes.BadRequest, "scheduleType 非法") }; } internal static string ToScheduleTypeText(MemberMessageScheduleType value) { return value switch { MemberMessageScheduleType.Immediate => "immediate", MemberMessageScheduleType.Scheduled => "scheduled", _ => "immediate" }; } internal static MemberMessageStatus ParseStatusOrNull(string? value) { return (value ?? string.Empty).Trim().ToLowerInvariant() switch { "draft" => MemberMessageStatus.Draft, "pending" => MemberMessageStatus.Pending, "sending" => MemberMessageStatus.Sending, "sent" => MemberMessageStatus.Sent, "failed" => MemberMessageStatus.Failed, _ => throw new BusinessException(ErrorCodes.BadRequest, "status 非法") }; } internal static MemberMessageStatus? TryParseStatus(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } return ParseStatusOrNull(value); } internal static string ToStatusText(MemberMessageStatus value) { return value switch { MemberMessageStatus.Draft => "draft", MemberMessageStatus.Pending => "pending", MemberMessageStatus.Sending => "sending", MemberMessageStatus.Sent => "sent", MemberMessageStatus.Failed => "failed", _ => "draft" }; } internal static MemberMessageChannel ParseChannel(string? value) { return (value ?? string.Empty).Trim().ToLowerInvariant() switch { "inapp" => MemberMessageChannel.InApp, "sms" => MemberMessageChannel.Sms, "wechat-mini" => MemberMessageChannel.WeChatMini, "wechat" => MemberMessageChannel.WeChatMini, _ => throw new BusinessException(ErrorCodes.BadRequest, "channel 非法") }; } internal static MemberMessageChannel? TryParseChannel(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } return ParseChannel(value); } internal static string ToChannelText(MemberMessageChannel value) { return value switch { MemberMessageChannel.InApp => "inapp", MemberMessageChannel.Sms => "sms", MemberMessageChannel.WeChatMini => "wechat-mini", _ => "inapp" }; } internal static string ToRecipientStatusText(MemberMessageRecipientStatus value) { return value switch { MemberMessageRecipientStatus.Pending => "pending", MemberMessageRecipientStatus.Sent => "sent", MemberMessageRecipientStatus.Failed => "failed", _ => "pending" }; } internal static MemberMessageTemplateCategory ParseTemplateCategory(string? value) { return (value ?? string.Empty).Trim().ToLowerInvariant() switch { "marketing" => MemberMessageTemplateCategory.Marketing, "notice" => MemberMessageTemplateCategory.Notice, "recall" => MemberMessageTemplateCategory.Recall, _ => throw new BusinessException(ErrorCodes.BadRequest, "category 非法") }; } internal static MemberMessageTemplateCategory? TryParseTemplateCategory(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } return ParseTemplateCategory(value); } internal static string ToTemplateCategoryText(MemberMessageTemplateCategory value) { return value switch { MemberMessageTemplateCategory.Marketing => "marketing", MemberMessageTemplateCategory.Notice => "notice", MemberMessageTemplateCategory.Recall => "recall", _ => "notice" }; } internal static IReadOnlyList NormalizeTags(IReadOnlyList? tags) { return (tags ?? []) .Select(item => (item ?? string.Empty).Trim()) .Where(item => !string.IsNullOrWhiteSpace(item)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(item => item, StringComparer.OrdinalIgnoreCase) .ToList(); } internal static IReadOnlyList NormalizeChannels(IReadOnlyList? channels) { var parsed = (channels ?? []) .Select(ParseChannel) .Distinct() .ToList(); if (parsed.Count == 0) { throw new BusinessException(ErrorCodes.BadRequest, "channels 不能为空"); } return parsed.Select(ToChannelText).ToList(); } internal static string SerializeStringArray(IReadOnlyList source) { return JsonSerializer.Serialize(source, JsonOptions); } internal static IReadOnlyList DeserializeStringArray(string? value) { if (string.IsNullOrWhiteSpace(value)) { return []; } try { return JsonSerializer.Deserialize>(value, JsonOptions)? .Select(item => (item ?? string.Empty).Trim()) .Where(item => !string.IsNullOrWhiteSpace(item)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList() ?? []; } catch { return []; } } internal static decimal ResolveRatePercent(int numerator, int denominator) { if (denominator <= 0 || numerator <= 0) { return 0; } return decimal.Round((decimal)numerator * 100m / denominator, 2, MidpointRounding.AwayFromZero); } internal static MemberMessageTemplateDto ToTemplateDto(MemberMessageTemplate source) { return new MemberMessageTemplateDto { TemplateId = source.Id, Name = source.Name, Category = ToTemplateCategoryText(source.Category), Content = source.Content, UsageCount = source.UsageCount, LastUsedAt = source.LastUsedAt }; } internal static MemberMessageReachRecipientDto ToRecipientDto(MemberReachRecipient source) { return new MemberMessageReachRecipientDto { MemberId = source.MemberId, Channel = ToChannelText(source.Channel), Status = ToRecipientStatusText(source.Status), Mobile = source.Mobile, OpenId = source.OpenId, SentAt = source.SentAt, ReadAt = source.ReadAt, ConvertedAt = source.ConvertedAt, ErrorMessage = source.ErrorMessage }; } }