using System.Net.Mail; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; namespace TakeoutSaaS.Application.App.Finance.Invoice; /// /// 发票模块映射与参数标准化。 /// internal static class FinanceInvoiceMapping { public static TenantInvoiceType ParseInvoiceTypeRequired(string? value) { return ParseInvoiceTypeOptional(value) ?? throw new BusinessException(ErrorCodes.BadRequest, "invoiceType 参数不合法"); } public static TenantInvoiceType? ParseInvoiceTypeOptional(string? value) { var normalized = (value ?? string.Empty).Trim().ToLowerInvariant(); if (string.IsNullOrWhiteSpace(normalized)) { return null; } return normalized switch { "normal" => TenantInvoiceType.Normal, "special" => TenantInvoiceType.Special, _ => throw new BusinessException(ErrorCodes.BadRequest, "invoiceType 参数不合法") }; } public static TenantInvoiceStatus? ParseStatusOptional(string? value) { var normalized = (value ?? string.Empty).Trim().ToLowerInvariant(); if (string.IsNullOrWhiteSpace(normalized)) { return null; } return normalized switch { "pending" => TenantInvoiceStatus.Pending, "issued" => TenantInvoiceStatus.Issued, "voided" => TenantInvoiceStatus.Voided, _ => throw new BusinessException(ErrorCodes.BadRequest, "status 参数不合法") }; } public static string ToInvoiceTypeText(TenantInvoiceType value) { return value switch { TenantInvoiceType.Normal => "normal", TenantInvoiceType.Special => "special", _ => "normal" }; } public static string ToInvoiceTypeDisplayText(TenantInvoiceType value) { return value switch { TenantInvoiceType.Normal => "普票", TenantInvoiceType.Special => "专票", _ => "普票" }; } public static string ToStatusText(TenantInvoiceStatus value) { return value switch { TenantInvoiceStatus.Pending => "pending", TenantInvoiceStatus.Issued => "issued", TenantInvoiceStatus.Voided => "voided", _ => "pending" }; } public static string ToStatusDisplayText(TenantInvoiceStatus value) { return value switch { TenantInvoiceStatus.Pending => "待开票", TenantInvoiceStatus.Issued => "已开票", TenantInvoiceStatus.Voided => "已作废", _ => "待开票" }; } public static string NormalizeCompanyName(string? value) { return NormalizeRequiredText(value, "companyName", 128); } public static string NormalizeApplicantName(string? value) { return NormalizeRequiredText(value, "applicantName", 64); } public static string NormalizeOrderNo(string? value) { return NormalizeRequiredText(value, "orderNo", 32); } public static string NormalizeTaxpayerNumber(string? value) { return NormalizeRequiredText(value, "taxpayerNumber", 64); } public static string? NormalizeOptionalTaxpayerNumber(string? value) { return NormalizeOptionalText(value, "taxpayerNumber", 64); } public static string? NormalizeOptionalKeyword(string? value) { return NormalizeOptionalText(value, "keyword", 64); } public static string? NormalizeOptionalEmail(string? value) { var normalized = NormalizeOptionalText(value, "contactEmail", 128); if (normalized is null) { return null; } try { _ = new MailAddress(normalized); return normalized; } catch (FormatException) { throw new BusinessException(ErrorCodes.BadRequest, "contactEmail 参数不合法"); } } public static string? NormalizeOptionalPhone(string? value) { return NormalizeOptionalText(value, "contactPhone", 32); } public static string? NormalizeOptionalRemark(string? value, string fieldName, int maxLength = 256) { return NormalizeOptionalText(value, fieldName, maxLength); } public static string NormalizeVoidReason(string? value) { return NormalizeRequiredText(value, "voidReason", 256); } public static decimal NormalizeAmount(decimal value) { if (value <= 0) { throw new BusinessException(ErrorCodes.BadRequest, "amount 参数不合法"); } return decimal.Round(value, 2, MidpointRounding.AwayFromZero); } public static decimal NormalizeAutoIssueMaxAmount(decimal value) { if (value <= 0) { throw new BusinessException(ErrorCodes.BadRequest, "autoIssueMaxAmount 参数不合法"); } return decimal.Round(value, 2, MidpointRounding.AwayFromZero); } public static (DateTime? StartUtc, DateTime? EndUtc) NormalizeDateRange(DateTime? startUtc, DateTime? endUtc) { DateTime? normalizedStart = null; DateTime? normalizedEnd = null; if (startUtc.HasValue) { var utcValue = NormalizeUtc(startUtc.Value); normalizedStart = new DateTime(utcValue.Year, utcValue.Month, utcValue.Day, 0, 0, 0, DateTimeKind.Utc); } if (endUtc.HasValue) { var utcValue = NormalizeUtc(endUtc.Value); normalizedEnd = new DateTime(utcValue.Year, utcValue.Month, utcValue.Day, 0, 0, 0, DateTimeKind.Utc) .AddDays(1) .AddTicks(-1); } if (normalizedStart.HasValue && normalizedEnd.HasValue && normalizedStart > normalizedEnd) { throw new BusinessException(ErrorCodes.BadRequest, "开始日期不能晚于结束日期"); } return (normalizedStart, normalizedEnd); } public static DateTime NormalizeUtc(DateTime value) { return value.Kind switch { DateTimeKind.Utc => value, DateTimeKind.Local => value.ToUniversalTime(), _ => DateTime.SpecifyKind(value, DateTimeKind.Utc) }; } public static string BuildInvoiceNo(DateTime nowUtc) { var utcNow = NormalizeUtc(nowUtc); return $"INV{utcNow:yyyyMMddHHmmssfff}{Random.Shared.Next(100, 999)}"; } private static string NormalizeRequiredText(string? value, string fieldName, int maxLength) { var normalized = (value ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(normalized)) { throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 参数不合法"); } if (normalized.Length > maxLength) { throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 长度不能超过 {maxLength}"); } return normalized; } private static string? NormalizeOptionalText(string? value, string fieldName, int maxLength) { var normalized = (value ?? string.Empty).Trim(); if (normalized.Length == 0) { return null; } if (normalized.Length > maxLength) { throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 长度不能超过 {maxLength}"); } return normalized; } }