using System.Text.RegularExpressions; namespace OASystem.Domain.Entities.Groups; /// /// 机票费用录入 /// [SugarTable("Grp_AirTicketReservations")] public class Grp_AirTicketReservations : EntityBase { /************************* 2026版数据结构 *************************/ /// /// 团组外键编号 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int DIId { get; set; } /// /// 记录类型:0-正常机票 1-退票记录 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int RecordType { get; set; } = 0; /// /// 关联的原始机票记录ID(退票记录指向原机票记录) /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int OriginalReservationId { get; set; } /// /// 航段代码描述 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")] public string FlightsDescription { get; set; } /// /// 航班基础信息(去程、联程、返程) /// [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(500)")] public List AirTicketBasicInfos { get; set; } = new List(); /// /// 客人名称 /// RecordType = 0 时,存储正常机票的客人名称;RecordType = 1 时,存储退票记录的客人名称(可能与原机票记录不同) /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")] public string ClientName { get; set; } /// /// 客户人数 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int ClientNum { get; set; } /// /// 舱类型(数据类型外键) /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int CType { get; set; } /// /// 机票全价 /// [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")] public decimal Price { get; set; } /// /// 币种 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int Currency { get; set; } /// /// 费用信息(含退票信息) /// [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(max)")] public List CustTicketInfos { get; set; } = new List(); /// /// 报价说明 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")] public string PriceDescription { get; set; } /************************* 2026版数据结构 *************************/ #region 旧版兼容以前的数据结构,2026版不使用 /// /// 航班号 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")] public string FlightsCode { get; set; } /// /// 城市A-B /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")] public string FlightsCity { get; set; } /// /// 航班日期 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")] public string FlightsDate { get; set; } /// /// 航班时间 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")] public string FlightsTime { get; set; } /// /// 抵达时间 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")] public string ArrivedTime { get; set; } /// /// 是否值机 /// 0 否 1 是 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int IsCheckIn { get; set; } /// /// 是否选座 /// 0 否 1 是 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int IsSetSeat { get; set; } /// /// 是否购买行李服务 /// 0 否 1 是 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int IsPackage { get; set; } /// /// 是否行李直挂 /// 0 否 1 是 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int IsBagHandle { get; set; } /// /// 是否火车票出票选座 /// 0 否 1 是 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int IsTrain { get; set; } /// /// 去程航班描述代码 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")] public string LeaveDescription { get; set; } /// /// 返程航班描述代码 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")] public string ReturnDescription { get; set; } /// /// 出票前报价 /// [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")] public decimal PrePrice { get; set; } /// /// 出票前报价币种 /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int PreCurrency { get; set; } /// /// 机票编号 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")] public string TicketNumber { get; set; } /// /// 机票票号 /// [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")] public string TicketCode { get; set; } /// /// 客人类型(数据类型外键) /// [SugarColumn(IsNullable = true, ColumnDataType = "int")] public int PassengerType { get; set; } #endregion } /// /// 机票基础信息 /// public class AirTicketBasicInfo { /// /// 序号 /// public int No { get; set; } /// /// 航班号 /// public string FlightsCode { get; set; } /// /// 城市A-B /// public string FlightsCity { get; set; } /// /// 航班日期 /// public string FlightsDate { get; set; } /// /// 航班时间 /// public string FlightsTime { get; set; } /// /// 抵达时间 /// public string ArrivedTime { get; set; } } /// /// 客户机票信息 /// public class CustTicketInfo { /// /// 客户ID /// public int ClientId { get; set; } /// /// 舱类型(数据类型外键) /// public int CType { get; set; } /// /// 实际价格 /// public decimal ActualPrice { get; set; } /// /// 机票票号 /// public string TicketCode { get; set; } /// /// 机票编号 /// public string TicketNumber { get; set; } /// /// 选中服务ID集合 /// public List SelectedServiceIds { get; set; } = new List(); /// /// 附加服务 json数组 /// public List AdditionalServices { get; set; } = new List(); /// /// 机票总费用 /// 实际价格 + 附加服务费 /// public decimal TotalTicketPrice { get; set; } /// /// 是否已退票 /// public bool IsRefund { get; set; } = false; /// /// 退票记录 /// public RefundRecord RefundRecord { get; set; } = new RefundRecord(); } /// /// 退票记录 /// public class RefundRecord { /// /// 退票金额 /// public decimal RefundAmount { get; set; } /// /// 不可退税费 /// public decimal NonRefundableTax { get; set; } /// /// 退票时间 /// public string RefundTime { get; set; } /// /// 退款账户 /// Setdata Id /// public int RefundAccount { get; set; } /// /// 退票原因 /// public string RefundReason { get; set; } } /// /// 附加服务 /// public class AdditionalService { /// /// 服务类型Id(Sys_SetData) /// public int ServiceTypeId { get; set; } /// /// 金额 /// public decimal Amount { get; set; } } /// /// 航班信息 /// public class FlightInfo { public int SequenceNo { get; set; } // 序号 public string FlightNumber { get; set; } // 航班号 public string CabinClass { get; set; } // 舱位等级 public string WeekDay { get; set; } // 星期(MO,TU,WE,TH,FR,SA,SU) public string DepartureDateRaw { get; set; } // 原始日期字符串 public DateTime DepartureDate { get; set; } // 起飞日期 public string DepartureTimeRaw { get; set; } // 原始起飞时间 public string DepartureTime { get; set; } // 格式化起飞时间 public string ArrivalTimeRaw { get; set; } // 原始到达时间 public string ArrivalTime { get; set; } // 格式化到达时间 public string DepartureAirport { get; set; } // 起飞机场 public string ArrivalAirport { get; set; } // 到达机场 public bool IsNextDayArrival { get; set; } // 是否次日到达 public int NextDayCount { get; set; } // 跨天天数(+1表示第二天) public string Terminal { get; set; } // 航站楼 public string AircraftType { get; set; } // 机型 public string Duration { get; set; } // 飞行时长 public string Status { get; set; } // 状态(HK1,KK1,TK1等) public string RawText { get; set; } // 原始文本 } /// /// 航班解析器 /// public static class FlightParser { #region 常量定义 /// /// 月份映射 /// private static readonly Dictionary MonthMap = new() { { "JAN", 1 }, { "FEB", 2 }, { "MAR", 3 }, { "APR", 4 }, { "MAY", 5 }, { "JUN", 6 }, { "JUL", 7 }, { "AUG", 8 }, { "SEP", 9 }, { "OCT", 10 }, { "NOV", 11 }, { "DEC", 12 } }; /// /// 星期映射 /// private static readonly Dictionary WeekDayMap = new() { { "MO", "周一" }, { "TU", "周二" }, { "WE", "周三" }, { "TH", "周四" }, { "FR", "周五" }, { "SA", "周六" }, { "SU", "周日" } }; #endregion #region 主解析方法 /// /// 解析航班文本(自动识别格式) /// public static List ParseFlights(string rawText, int year = 2024) { if (string.IsNullOrWhiteSpace(rawText)) return new List(); var lines = rawText.Trim().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); var result = new List(); foreach (var line in lines) { var flight = ParseLine(line, year); if (flight != null) { result.Add(flight); } } return result.OrderBy(x => x.SequenceNo).ToList(); } /// /// 解析单行文本(自动识别格式) /// private static FlightInfo ParseLine(string line, int year) { line = line.Trim(); if (string.IsNullOrEmpty(line)) return null; // 格式1:HU7086 C MO18MAY TFUHAK HK1 1605 1820 E T2T2 if (line.Contains("HK1") || line.Contains("KK1") || line.Contains("TK1")) { return ParseFormat1(line, year); } // 格式2:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M // 格式2变体:2. LH098 TU28MAY FRAMUC 0915 1010 1 2 321 00H55M if (Regex.IsMatch(line, @"^\d+\.[A-Z0-9]+\s+[A-Z]{2}\d{2}[A-Z]{3}")) { return ParseFormat2(line, year); } return null; } #endregion #region 格式1解析 /// /// 解析格式1 /// private static FlightInfo ParseFormat1(string line, int year) { // 提取序号 var seqMatch = Regex.Match(line, @"^(\d+)\.\s*"); if (!seqMatch.Success) return null; var sequenceNo = int.Parse(seqMatch.Groups[1].Value); var content = line.Substring(seqMatch.Length).Trim(); // 正则匹配 var pattern = @"^([A-Z0-9]+)\s+([A-Z])\s+([A-Z]{2})(\d{2}[A-Z]{3})\s+([A-Z]{3})([A-Z]{3})\s+([A-Z]{2,3}\d?)\s+(\d{3,4})\s+(\d{3,4})(?:\+(\d+))?\s+([A-Z])\s+([A-Z0-9]+)"; var match = Regex.Match(content, pattern); if (!match.Success) return null; var weekDay = match.Groups[3].Value; var dateRaw = match.Groups[4].Value; var departureAirport = match.Groups[5].Value; var arrivalAirport = match.Groups[6].Value; var departureTime = match.Groups[8].Value; var arrivalTime = match.Groups[9].Value; var isNextDay = match.Groups[10].Success && match.Groups[10].Value == "1"; var terminalFlag = match.Groups[12].Value; return new FlightInfo { SequenceNo = sequenceNo, FlightNumber = match.Groups[1].Value, CabinClass = match.Groups[2].Value, WeekDay = weekDay, DepartureDateRaw = $"{weekDay}{dateRaw}", DepartureAirport = departureAirport, ArrivalAirport = arrivalAirport, DepartureTimeRaw = departureTime, ArrivalTimeRaw = arrivalTime, IsNextDayArrival = isNextDay, DepartureTime = FormatTime(departureTime), ArrivalTime = FormatTime(arrivalTime), DepartureDate = ParseDate(dateRaw, year), Status = match.Groups[7].Value, Terminal = terminalFlag, RawText = line }; } #endregion #region 格式2解析(修复版) /// /// 解析格式2 /// 示例:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M /// private static FlightInfo ParseFormat2(string line, int year) { // 1. 提取序号(支持 "1." 和 "1. ") var seqMatch = Regex.Match(line, @"^(\d+)\.\s*"); if (!seqMatch.Success) return null; var sequenceNo = int.Parse(seqMatch.Groups[1].Value); var content = line.Substring(seqMatch.Length).Trim(); // 2. 分割空格 var parts = content.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 8) return null; try { var flight = new FlightInfo { SequenceNo = sequenceNo, RawText = line }; int idx = 0; // 航班号 flight.FlightNumber = parts[idx++]; // 日期(如 TU27MAY) var dateRaw = parts[idx++]; if (dateRaw.Length >= 7) { flight.WeekDay = dateRaw.Substring(0, 2); flight.DepartureDateRaw = dateRaw; } // 机场(如 TFUFRA 或分开的格式) var airportStr = parts[idx++]; if (airportStr.Length >= 6) { // 连写格式:TFUFRA -> TFU + FRA flight.DepartureAirport = airportStr.Substring(0, 3); flight.ArrivalAirport = airportStr.Substring(3, 3); } else { // 分开格式 flight.DepartureAirport = airportStr; flight.ArrivalAirport = parts[idx++]; } // 起飞时间 flight.DepartureTimeRaw = parts[idx++]; flight.DepartureTime = FormatTime(flight.DepartureTimeRaw); // 到达时间(可能带 +1) var arrivalRaw = parts[idx++]; if (arrivalRaw.Contains('+')) { var arrParts = arrivalRaw.Split('+'); flight.ArrivalTimeRaw = arrParts[0]; flight.IsNextDayArrival = true; if (arrParts.Length > 1 && int.TryParse(arrParts[1], out var days)) { flight.NextDayCount = days; } } else { flight.ArrivalTimeRaw = arrivalRaw; } flight.ArrivalTime = FormatTime(flight.ArrivalTimeRaw); // 航站楼(可选,以T开头) if (idx < parts.Length && parts[idx].StartsWith("T")) { flight.Terminal = parts[idx++]; } // 序号(可选) if (idx < parts.Length && int.TryParse(parts[idx], out _)) { // 这个序号可能是经停次数或行程序号 idx++; } // 机型(可选,数字或数字+字母) if (idx < parts.Length && Regex.IsMatch(parts[idx], @"^\d{2,3}[A-Z]?$")) { flight.AircraftType = parts[idx++]; } // 飞行时长(可选,包含H) if (idx < parts.Length && parts[idx].Contains('H')) { flight.Duration = parts[idx++]; } // 解析日期 flight.DepartureDate = ParseDate(dateRaw, year); return flight; } catch { return null; } } #endregion #region 辅助方法 /// /// 格式化时间(1905 -> 19:05) /// private static string FormatTime(string time) { if (string.IsNullOrWhiteSpace(time)) return string.Empty; // 移除 +1 等后缀 var cleanTime = time.Split('+')[0]; // 4位数字 if (cleanTime.Length == 4 && int.TryParse(cleanTime, out _)) { return $"{cleanTime.Substring(0, 2)}:{cleanTime.Substring(2, 2)}"; } // 3位数字(905 -> 09:05) if (cleanTime.Length == 3 && int.TryParse(cleanTime, out _)) { return $"0{cleanTime.Substring(0, 1)}:{cleanTime.Substring(1, 2)}"; } // 已经是 HH:mm 格式 if (Regex.IsMatch(cleanTime, @"^\d{1,2}:\d{2}$")) { var parts = cleanTime.Split(':'); return $"{parts[0].PadLeft(2, '0')}:{parts[1]}"; } return cleanTime; } /// /// 解析日期(27MAY -> 2024-05-27) /// private static DateTime ParseDate(string dateStr, int year) { if (string.IsNullOrWhiteSpace(dateStr)) return DateTime.MinValue; // 移除星期前缀 var cleanDate = Regex.Replace(dateStr, @"^[A-Z]{2}", ""); var match = Regex.Match(cleanDate, @"(\d{2})([A-Z]{3})"); if (match.Success) { var day = int.Parse(match.Groups[1].Value); var monthStr = match.Groups[2].Value; if (MonthMap.TryGetValue(monthStr.ToUpper(), out var month)) { try { return new DateTime(year, month, day); } catch { return DateTime.MinValue; } } } return DateTime.MinValue; } /// /// 获取完整起飞时间(含时分) /// public static DateTime GetFullDepartureTime(FlightInfo flight, int year) { if (flight.DepartureDate == DateTime.MinValue || string.IsNullOrEmpty(flight.DepartureTimeRaw)) return DateTime.MinValue; var time = ParseTimeToTimeSpan(flight.DepartureTimeRaw); return flight.DepartureDate.Add(time); } /// /// 获取完整到达时间(含时分和跨天) /// public static DateTime GetFullArrivalTime(FlightInfo flight, int year) { if (flight.DepartureDate == DateTime.MinValue || string.IsNullOrEmpty(flight.ArrivalTimeRaw)) return DateTime.MinValue; var time = ParseTimeToTimeSpan(flight.ArrivalTimeRaw); var arrivalDate = flight.DepartureDate.Add(time); if (flight.IsNextDayArrival) { arrivalDate = arrivalDate.AddDays(flight.NextDayCount > 0 ? flight.NextDayCount : 1); } return arrivalDate; } /// /// 解析时间字符串为 TimeSpan /// private static TimeSpan ParseTimeToTimeSpan(string time) { var cleanTime = time.Split('+')[0]; if (cleanTime.Length == 4) { return new TimeSpan( int.Parse(cleanTime.Substring(0, 2)), int.Parse(cleanTime.Substring(2, 2)), 0); } if (cleanTime.Length == 3) { return new TimeSpan( int.Parse(cleanTime.Substring(0, 1)), int.Parse(cleanTime.Substring(1, 2)), 0); } return TimeSpan.Zero; } /// /// 获取星期中文名称 /// public static string GetWeekDayName(string weekDay) { return WeekDayMap.TryGetValue(weekDay.ToUpper(), out var name) ? name : weekDay; } #endregion }