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 IsNew { 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 string DepartureTerminal { get; set; } /// /// 抵达航站楼 /// public string ArrivalTerminal { get; set; } /// /// 机型(如:359、321、32A、73H) /// public string AircraftType { get; set; } /// /// 耗时(如:11H10M、00H55M、01H35M) /// public string Duration { 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 { //public int Id { get; set; } /// /// 服务类型Id(Sys_SetData) /// public int ServiceTypeId { get; set; } /// /// 金额 /// public decimal Amount { get; set; } /// /// 报价说明 /// public string PriceDescription { get; set; } #region Credit Info /// /// 支付方式 /// public int PayDId { get; set; } /// /// 消费方式 /// public string ConsumptionPatterns { get; set; } /// /// 消费日期 /// public string ConsumptionDate { get; set; } /// /// 付款金额 /// public decimal PayMoney { get; set; } /// /// 付款币种 数据类型Id /// public int PaymentCurrency { get; set; } /// /// 卡类型 /// public int CTDId { get; set; } /// /// 银行卡号 /// public string BankNo { get; set; } /// /// 持卡人姓名 /// public string CardholderName { get; set; } /// /// 当天汇率 /// public decimal DayRate { get; set; } /// /// 公司银行卡号 /// public string CompanyBankNo { get; set; } /// /// 对方开户行 /// public string OtherBankName { get; set; } /// /// 对方银行账号 /// public string OtherSideNo { get; set; } /// /// 对方姓名 /// public string OtherSideName { get; set; } /// 收款方 /// public string Payee { get; set; } /// /// 费用标识 /// 0 公转 1 私转 /// public int OrbitalPrivateTransfer { get; set; } /// /// 备注 /// public string Remark { get; set; } /// /// 总经理是否审核 /// 0 未审核 /// 1 已通过 /// 2 未通过 /// 3 自动审核 /// public int IsAuditGM { get; set; } = 0; /// /// 审核状态描述 /// public string IsAuditGMStr => IsAuditGM switch { 0 => "未审核", 1 => "已通过", 2 => "未通过", 3 => "自动审核", _ => "未知状态" }; /// /// 总经理审核人 /// public int AuditGMOperate { get; set; } /// /// 总经理审核时间 /// public string AuditGMDate { get; set; } /// /// 审核描述 /// public string AuditStr { get; set; } #endregion } /// /// 航班信息 /// public class FlightInfo { /// /// 航段序号 /// public int SequenceNo { get; set; } /// /// 航班号 /// 例如:CA431 /// public string FlightNumber { get; set; } /// /// 星期 /// 例如:TU /// public string WeekDay { get; set; } /// /// 起飞日期 /// public DateTime DepartureDate { get; set; } /// /// 出发机场三字码 /// 例如:TFU /// public string DepartureAirport { get; set; } /// /// 到达机场三字码 /// 例如:FRA /// public string ArrivalAirport { get; set; } /// /// 起飞时间 /// 格式:HH:mm /// public string DepartureTime { get; set; } /// /// 到达时间 /// 格式:HH:mm /// public string ArrivalTime { get; set; } /// /// 是否跨天到达 /// public bool IsNextDayArrival { get; set; } /// /// 跨天数量 /// 例如: /// +1 表示次日到达 /// +2 表示第三天到达 /// public int NextDayCount { get; set; } /// /// 出发航站楼 /// 例如:T1 /// public string DepartureTerminal { get; set; } /// /// 到达航站楼 /// 例如:T2 /// public string ArrivalTerminal { get; set; } /// /// 机型代码 /// 例如: /// 359=A350-900 /// 321=A321 /// 32A=A320neo /// public string AircraftType { get; set; } /// /// 飞行时长 /// 例如:11H10M /// public string Duration { get; set; } /// /// 原始文本 /// public string RawText { get; set; } } /// /// 航班解析器 - 支持多种格式 /// public static class FlightParser { 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 }; public static List ParseFlights(string text) { var result = new List(); if (string.IsNullOrWhiteSpace(text)) return result; // 匹配所有以数字开头的行 var matches = Regex.Matches( text, @"\d+\..*?(?=\d+\.|$)", RegexOptions.Singleline); foreach (Match match in matches) { var flight = ParseLine(match.Value.Trim()); if (flight != null) { result.Add(flight); } } result = result.OrderBy(x => x.SequenceNo).ToList(); FixYear(result); return result; } private static FlightInfo ParseLine(string line) { var seqMatch = Regex.Match(line, @"^(\d+)\."); if (!seqMatch.Success) return null; var flight = new FlightInfo { SequenceNo = int.Parse(seqMatch.Groups[1].Value), RawText = line }; string content = line.Substring(seqMatch.Length).Trim(); var parts = Regex.Split(content, @"\s+") .Where(p => !string.IsNullOrWhiteSpace(p)) .ToList(); if (parts.Count < 5) return null; int idx = 0; // 1. 航班号 flight.FlightNumber = parts[idx++]; // 2. 跳过可选的舱位代码(单个大写字母) if (idx < parts.Count && Regex.IsMatch(parts[idx], @"^[A-Z]$")) { idx++; } // 3. 解析日期(格式:TU23JUN 或 TU27MAY) if (idx >= parts.Count) return null; string dateText = parts[idx++]; // 提取星期和日期 var dateMatch = Regex.Match(dateText, @"^([A-Z]{2})(\d{2})([A-Z]{3})$"); if (dateMatch.Success) { flight.WeekDay = dateMatch.Groups[1].Value; flight.DepartureDate = ParseDate( int.Parse(dateMatch.Groups[2].Value), dateMatch.Groups[3].Value ); } else { // 兼容旧格式:TU27MAY 直接解析 flight.WeekDay = dateText.Length >= 2 ? dateText[..2] : ""; flight.DepartureDate = ParseMonthDay(dateText); } // 4. 航线(6位:出发+到达) if (idx >= parts.Count) return null; string route = parts[idx++]; if (route.Length >= 6) { flight.DepartureAirport = route[..3]; flight.ArrivalAirport = route.Substring(3, 3); } // 5. 跳过预订状态码(如 HK4, HK, KL 等) if (idx < parts.Count && Regex.IsMatch(parts[idx], @"^[A-Z]{2}\d*$")) { idx++; } // 6. 起飞时间 if (idx >= parts.Count) return null; flight.DepartureTime = FormatTime(parts[idx++]); // 7. 到达时间 if (idx >= parts.Count) return null; string arrivalRaw = parts[idx++]; ParseArrivalTime(arrivalRaw, flight); // 8. 跳过可选的舱位代码(如 E) if (idx < parts.Count && Regex.IsMatch(parts[idx], @"^[A-Z]$")) { idx++; } // 9. 解析剩余字段(航站楼、机型、飞行时长) var remain = parts.Skip(idx).ToList(); ParseRemainFields(remain, flight); return flight; } /// /// 解析到达时间(支持 +1 格式) /// private static void ParseArrivalTime(string arrivalRaw, FlightInfo flight) { if (arrivalRaw.Contains('+')) { var arr = arrivalRaw.Split('+'); flight.ArrivalTime = FormatTime(arr[0]); flight.IsNextDayArrival = true; if (arr.Length > 1 && int.TryParse(arr[1], out int days)) { flight.NextDayCount = days; } } else { flight.ArrivalTime = FormatTime(arrivalRaw); } } /// /// 解析剩余字段(航站楼、机型、飞行时长) /// private static void ParseRemainFields(List remain, FlightInfo flight) { if (remain.Count == 0) return; // 从后往前解析 int idx = remain.Count - 1; // 1. 最后一个字段:飞行时长(格式:11H10M 或 00H55M) if (idx >= 0 && Regex.IsMatch(remain[idx], @"^\d{2}H\d{2}M$")) { flight.Duration = remain[idx]; idx--; } // 2. 倒数第二个字段:机型(格式:359, 321, 32A, 737 等) if (idx >= 0 && Regex.IsMatch(remain[idx], @"^[A-Z]?\d{2,3}[A-Z]?$")) { flight.AircraftType = remain[idx]; idx--; } // 3. 剩余字段解析航站楼 // 情况1:一个航站楼(出发和到达相同) if (idx == 0) { flight.DepartureTerminal = NormalizeTerminal(remain[0]); flight.ArrivalTerminal = flight.DepartureTerminal; } // 情况2:两个航站楼(出发和到达不同) else if (idx >= 1) { // 检查是否是两个航站楼 string terminal1 = remain[0]; string terminal2 = remain[1]; // 如果两个都是航站楼格式(T1, T2, 1, 2, T3等) if (IsTerminalFormat(terminal1) && IsTerminalFormat(terminal2)) { flight.DepartureTerminal = NormalizeTerminal(terminal1); flight.ArrivalTerminal = NormalizeTerminal(terminal2); } // 如果第一个是航站楼,第二个不是,则只解析第一个 else if (IsTerminalFormat(terminal1)) { flight.DepartureTerminal = NormalizeTerminal(terminal1); } } } /// /// 判断是否为航站楼格式 /// private static bool IsTerminalFormat(string value) { if (string.IsNullOrWhiteSpace(value)) return false; value = value.Trim().ToUpper(); // 匹配:T1, T2, T3, 1, 2, 3 等 return Regex.IsMatch(value, @"^T?\d+$"); } /// /// 标准化航站楼 /// private static string NormalizeTerminal(string terminal) { if (string.IsNullOrWhiteSpace(terminal)) return null; terminal = terminal.Trim().ToUpper(); // 纯数字补 T if (Regex.IsMatch(terminal, @"^\d+$")) { return $"T{terminal}"; } return terminal; } /// /// 格式化时间 /// private static string FormatTime(string time) { if (string.IsNullOrWhiteSpace(time)) return string.Empty; time = time.Split('+')[0]; if (time.Length == 4) { return $"{time[..2]}:{time.Substring(2, 2)}"; } if (time.Length == 3) { return $"0{time[0]}:{time.Substring(1, 2)}"; } return time; } /// /// 解析日期 /// private static DateTime ParseDate(int day, string month) { if (!MonthMap.TryGetValue(month.ToUpper(), out int monthNum)) return DateTime.MinValue; return new DateTime(DateTime.Now.Year, monthNum, day); } /// /// 解析月日(兼容旧格式) /// private static DateTime ParseMonthDay(string value) { value = Regex.Replace(value, @"^[A-Z]{2}", ""); var match = Regex.Match(value, @"(\d{2})([A-Z]{3})"); if (!match.Success) return DateTime.MinValue; int day = int.Parse(match.Groups[1].Value); if (!MonthMap.TryGetValue(match.Groups[2].Value, out int month)) return DateTime.MinValue; return new DateTime(DateTime.Now.Year, month, day); } /// /// 自动修正跨年 /// private static void FixYear(List flights) { if (flights.Count <= 1) return; int currentYear = DateTime.Now.Year; DateTime previousDate = DateTime.MinValue; foreach (var flight in flights) { if (flight.DepartureDate == DateTime.MinValue) continue; var date = new DateTime( currentYear, flight.DepartureDate.Month, flight.DepartureDate.Day); if (previousDate != DateTime.MinValue && date < previousDate) { currentYear++; date = new DateTime( currentYear, flight.DepartureDate.Month, flight.DepartureDate.Day); } flight.DepartureDate = date; previousDate = date; } } }