| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707 |
- using System.Text.RegularExpressions;
- namespace OASystem.Domain.Entities.Groups;
- /// <summary>
- /// 机票费用录入
- /// </summary>
- [SugarTable("Grp_AirTicketReservations")]
- public class Grp_AirTicketReservations : EntityBase
- {
- /************************* 2026版数据结构 *************************/
- /// <summary>
- /// 团组外键编号
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int DIId { get; set; }
- /// <summary>
- /// 记录类型:0-正常机票 1-退票记录
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int RecordType { get; set; } = 0;
- /// <summary>
- /// 关联的原始机票记录ID(退票记录指向原机票记录)
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int OriginalReservationId { get; set; }
- /// <summary>
- /// 航段代码描述
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
- public string FlightsDescription { get; set; }
- /// <summary>
- /// 航班基础信息(去程、联程、返程)
- /// </summary>
- [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(500)")]
- public List<AirTicketBasicInfo> AirTicketBasicInfos { get; set; } = new List<AirTicketBasicInfo>();
- /// <summary>
- /// 客人名称
- /// RecordType = 0 时,存储正常机票的客人名称;RecordType = 1 时,存储退票记录的客人名称(可能与原机票记录不同)
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
- public string ClientName { get; set; }
- /// <summary>
- /// 客户人数
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int ClientNum { get; set; }
- /// <summary>
- /// 舱类型(数据类型外键)
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int CType { get; set; }
- /// <summary>
- /// 机票全价
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
- public decimal Price { get; set; }
- /// <summary>
- /// 币种
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int Currency { get; set; }
- /// <summary>
- /// 费用信息(含退票信息)
- /// </summary>
- [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(max)")]
- public List<CustTicketInfo> CustTicketInfos { get; set; } = new List<CustTicketInfo>();
- /// <summary>
- /// 报价说明
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
- public string PriceDescription { get; set; }
- /************************* 2026版数据结构 *************************/
- #region 旧版兼容以前的数据结构,2026版不使用
- /// <summary>
- /// 航班号
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
- public string FlightsCode { get; set; }
- /// <summary>
- /// 城市A-B
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
- public string FlightsCity { get; set; }
- /// <summary>
- /// 航班日期
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
- public string FlightsDate { get; set; }
- /// <summary>
- /// 航班时间
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
- public string FlightsTime { get; set; }
- /// <summary>
- /// 抵达时间
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(30)")]
- public string ArrivedTime { get; set; }
- /// <summary>
- /// 是否值机
- /// 0 否 1 是
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int IsCheckIn { get; set; }
- /// <summary>
- /// 是否选座
- /// 0 否 1 是
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int IsSetSeat { get; set; }
- /// <summary>
- /// 是否购买行李服务
- /// 0 否 1 是
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int IsPackage { get; set; }
- /// <summary>
- /// 是否行李直挂
- /// 0 否 1 是
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int IsBagHandle { get; set; }
- /// <summary>
- /// 是否火车票出票选座
- /// 0 否 1 是
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int IsTrain { get; set; }
- /// <summary>
- /// 去程航班描述代码
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
- public string LeaveDescription { get; set; }
- /// <summary>
- /// 返程航班描述代码
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
- public string ReturnDescription { get; set; }
- /// <summary>
- /// 出票前报价
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
- public decimal PrePrice { get; set; }
- /// <summary>
- /// 出票前报价币种
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int PreCurrency { get; set; }
-
- /// <summary>
- /// 机票编号
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
- public string TicketNumber { get; set; }
- /// <summary>
- /// 机票票号
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "varchar(100)")]
- public string TicketCode { get; set; }
- /// <summary>
- /// 客人类型(数据类型外键)
- /// </summary>
- [SugarColumn(IsNullable = true, ColumnDataType = "int")]
- public int PassengerType { get; set; }
- #endregion
- }
- /// <summary>
- /// 机票基础信息
- /// </summary>
- public class AirTicketBasicInfo
- {
- /// <summary>
- /// 序号
- /// </summary>
- public int No { get; set; }
- /// <summary>
- /// 航班号
- /// </summary>
- public string FlightsCode { get; set; }
- /// <summary>
- /// 城市A-B
- /// </summary>
- public string FlightsCity { get; set; }
- /// <summary>
- /// 航班日期
- /// </summary>
- public string FlightsDate { get; set; }
- /// <summary>
- /// 航班时间
- /// </summary>
- public string FlightsTime { get; set; }
- /// <summary>
- /// 抵达时间
- /// </summary>
- public string ArrivedTime { get; set; }
- }
- /// <summary>
- /// 客户机票信息
- /// </summary>
- public class CustTicketInfo
- {
- /// <summary>
- /// 客户ID
- /// </summary>
- public int ClientId { get; set; }
- /// <summary>
- /// 舱类型(数据类型外键)
- /// </summary>
- public int CType { get; set; }
- /// <summary>
- /// 实际价格
- /// </summary>
- public decimal ActualPrice { get; set; }
- /// <summary>
- /// 机票票号
- /// </summary>
- public string TicketCode { get; set; }
- /// <summary>
- /// 机票编号
- /// </summary>
- public string TicketNumber { get; set; }
- /// <summary>
- /// 选中服务ID集合
- /// </summary>
- public List<int> SelectedServiceIds { get; set; } = new List<int>();
- /// <summary>
- /// 附加服务 json数组
- /// </summary>
- public List<AdditionalService> AdditionalServices { get; set; } = new List<AdditionalService>();
- /// <summary>
- /// 机票总费用
- /// 实际价格 + 附加服务费
- /// </summary>
- public decimal TotalTicketPrice { get; set; }
- /// <summary>
- /// 是否已退票
- /// </summary>
- public bool IsRefund { get; set; } = false;
- /// <summary>
- /// 退票记录
- /// </summary>
- public RefundRecord? RefundRecord { get; set; }
- }
- /// <summary>
- /// 退票记录
- /// </summary>
- public class RefundRecord
- {
- /// <summary>
- /// 退票金额
- /// </summary>
- public decimal RefundAmount { get; set; }
- /// <summary>
- /// 不可退税费
- /// </summary>
- public decimal NonRefundableTax { get; set; }
- /// <summary>
- /// 退票时间
- /// </summary>
- public string RefundTime { get; set; }
- /// <summary>
- /// 退款账户
- /// Setdata Id
- /// </summary>
- public int RefundAccount { get; set; }
- /// <summary>
- /// 退票原因
- /// </summary>
- public string RefundReason { get; set; }
- }
- /// <summary>
- /// 附加服务
- /// </summary>
- public class AdditionalService
- {
- /// <summary>
- /// 服务类型Id(Sys_SetData)
- /// </summary>
- public int ServiceTypeId { get; set; }
- /// <summary>
- /// 金额
- /// </summary>
- public decimal Amount { get; set; }
- }
- /// <summary>
- /// 航班信息实体(通用)
- /// </summary>
- public class FlightInfo
- {
- /// <summary>
- /// 序号
- /// </summary>
- public int SequenceNo { get; set; }
- /// <summary>
- /// 航班号
- /// </summary>
- public string FlightNumber { get; set; }
- /// <summary>
- /// 舱位等级/舱位代码
- /// </summary>
- public string CabinClass { get; set; }
- /// <summary>
- /// 星期
- /// </summary>
- public string WeekDay { get; set; }
- /// <summary>
- /// 出发日期(原始格式)
- /// </summary>
- public string DepartureDateRaw { get; set; }
- /// <summary>
- /// 出发日期(标准格式 yyyy-MM-dd)
- /// </summary>
- public string DepartureDate { get; set; }
- /// <summary>
- /// 出发机场代码
- /// </summary>
- public string DepartureAirport { get; set; }
- /// <summary>
- /// 到达机场代码
- /// </summary>
- public string ArrivalAirport { get; set; }
- /// <summary>
- /// 出发时间(原始格式)
- /// </summary>
- public string DepartureTimeRaw { get; set; }
- /// <summary>
- /// 出发时间(格式化 HH:mm)
- /// </summary>
- public string DepartureTime { get; set; }
- /// <summary>
- /// 到达时间(原始格式)
- /// </summary>
- public string ArrivalTimeRaw { get; set; }
- /// <summary>
- /// 到达时间(格式化 HH:mm)
- /// </summary>
- public string ArrivalTime { get; set; }
- /// <summary>
- /// 是否次日到达
- /// </summary>
- public bool IsNextDayArrival { get; set; }
- /// <summary>
- /// 航站楼信息
- /// </summary>
- public string Terminal { get; set; }
- /// <summary>
- /// 机型
- /// </summary>
- public string AircraftType { get; set; }
- /// <summary>
- /// 飞行时长
- /// </summary>
- public string Duration { get; set; }
- /// <summary>
- /// 状态(HK/KK等)
- /// </summary>
- public string Status { get; set; }
- /// <summary>
- /// 原始文本
- /// </summary>
- public string RawText { get; set; }
- /// <summary>
- /// 显示信息
- /// </summary>
- public string DisplayInfo => $"{SequenceNo}.{FlightNumber} {DepartureAirport}→{ArrivalAirport} {DepartureTime}→{ArrivalTime}{(IsNextDayArrival ? "+1" : "")}";
- }
- /// <summary>
- /// 航班解析器
- /// </summary>
- public static class FlightParser
- {
- #region 常量定义
- /// <summary>
- /// 月份映射
- /// </summary>
- private static readonly Dictionary<string, int> 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 }
- };
- /// <summary>
- /// 星期映射
- /// </summary>
- private static readonly Dictionary<string, string> WeekDayMap = new()
- {
- { "MO", "周一" }, { "TU", "周二" }, { "WE", "周三" }, { "TH", "周四" },
- { "FR", "周五" }, { "SA", "周六" }, { "SU", "周日" }
- };
- #endregion
- #region 主解析方法
- /// <summary>
- /// 解析航班文本(自动识别格式)
- /// </summary>
- public static List<FlightInfo> ParseFlights(string rawText, int year = 2024)
- {
- if (string.IsNullOrWhiteSpace(rawText))
- return new List<FlightInfo>();
- var lines = rawText.Trim().Split(new[] { '\n', '\r' ,','}, StringSplitOptions.RemoveEmptyEntries);
- var result = new List<FlightInfo>();
- foreach (var line in lines)
- {
- var flight = ParseLine(line, year);
- if (flight != null)
- {
- result.Add(flight);
- }
- }
- return result.OrderBy(x => x.SequenceNo).ToList();
- }
- /// <summary>
- /// 解析单行文本(自动识别格式)
- /// </summary>
- 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
- 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解析(HU7086 C MO18MAY TFUHAK HK1 1605 1820 E T2T2)
- /// <summary>
- /// 解析格式1
- /// 示例:1. HU7086 C MO18MAY TFUHAK HK1 1605 1820 E T2T2
- /// </summary>
- 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; // MO
- var dateRaw = match.Groups[4].Value; // 18MAY
- var departureAirport = match.Groups[5].Value; // TFU
- var arrivalAirport = match.Groups[6].Value; // HAK
- var status = match.Groups[7].Value; // HK1
- var departureTime = match.Groups[8].Value; // 1605
- var arrivalTime = match.Groups[9].Value; // 1820
- var isNextDay = match.Groups[10].Success && match.Groups[10].Value == "1";
- var terminalFlag = match.Groups[12].Value; // T2T2
- var flight = 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,
- Status = status,
- Terminal = terminalFlag,
- RawText = line
- };
- // 格式化时间
- flight.DepartureTime = FormatTime(departureTime);
- flight.ArrivalTime = FormatTime(arrivalTime);
- // 解析日期
- flight.DepartureDate = ParseDate(dateRaw, year);
- return flight;
- }
- #endregion
- #region 格式2解析(1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M)
- /// <summary>
- /// 解析格式2
- /// 示例:1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
- /// </summary>
- private static FlightInfo ParseFormat2(string line, int year)
- {
- // 提取序号和航班号
- var pattern = @"^(\d+)\.([A-Z0-9]+)\s+([A-Z]{2})(\d{2}[A-Z]{3})\s+([A-Z]{3})([A-Z]{3})\s+(\d{3,4})\s+(\d{3,4})(?:\+(\d+))?\s+([A-Z0-9]+)\s+(\d+)\s+([A-Z0-9]+)\s+([\dH]+)";
- var match = Regex.Match(line.Trim(), pattern);
- if (!match.Success) return null;
- var weekDay = match.Groups[3].Value; // TU
- var dateRaw = match.Groups[4].Value; // 27MAY
- var departureAirport = match.Groups[5].Value; // TFU
- var arrivalAirport = match.Groups[6].Value; // FRA
- var departureTime = match.Groups[7].Value; // 0135
- var arrivalTime = match.Groups[8].Value; // 0645
- var isNextDay = match.Groups[9].Success && match.Groups[9].Value == "1";
- var terminal = match.Groups[10].Value; // T1
- var stopCount = match.Groups[11].Value; // 1
- var aircraftType = match.Groups[12].Value; // 359
- var duration = match.Groups[13].Value; // 11H10M
- var flight = new FlightInfo
- {
- SequenceNo = int.Parse(match.Groups[1].Value),
- FlightNumber = match.Groups[2].Value,
- WeekDay = weekDay,
- DepartureDateRaw = $"{weekDay}{dateRaw}",
- DepartureAirport = departureAirport,
- ArrivalAirport = arrivalAirport,
- DepartureTimeRaw = departureTime,
- ArrivalTimeRaw = arrivalTime,
- IsNextDayArrival = isNextDay,
- Terminal = terminal,
- AircraftType = aircraftType,
- Duration = duration,
- RawText = line
- };
- // 格式化时间
- flight.DepartureTime = FormatTime(departureTime);
- flight.ArrivalTime = FormatTime(arrivalTime);
- // 解析日期
- flight.DepartureDate = ParseDate(dateRaw, year);
- return flight;
- }
- #endregion
- #region 辅助方法
- /// <summary>
- /// 格式化时间(1905 -> 19:05)
- /// </summary>
- private static string FormatTime(string time)
- {
- if (string.IsNullOrWhiteSpace(time))
- return string.Empty;
- // 4位数字
- if (time.Length == 4 && int.TryParse(time, out _))
- {
- return $"{time.Substring(0, 2)}:{time.Substring(2, 2)}";
- }
- // 3位数字(905 -> 09:05)
- if (time.Length == 3 && int.TryParse(time, out _))
- {
- return $"0{time.Substring(0, 1)}:{time.Substring(1, 2)}";
- }
- // 已经是 HH:mm 格式
- if (Regex.IsMatch(time, @"^\d{1,2}:\d{2}$"))
- {
- var parts = time.Split(':');
- return $"{parts[0].PadLeft(2, '0')}:{parts[1]}";
- }
- return time;
- }
- /// <summary>
- /// 解析日期(27MAY -> 2024-05-27)
- /// </summary>
- private static string ParseDate(string dateStr, int year)
- {
- if (string.IsNullOrWhiteSpace(dateStr))
- return string.Empty;
- var match = Regex.Match(dateStr, @"(\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
- {
- var date = new DateTime(year, month, day);
- return date.ToString("yyyy-MM-dd");
- }
- catch
- {
- return dateStr;
- }
- }
- }
- return dateStr;
- }
- /// <summary>
- /// 获取星期中文名称
- /// </summary>
- public static string GetWeekDayName(string weekDay)
- {
- return WeekDayMap.TryGetValue(weekDay.ToUpper(), out var name) ? name : weekDay;
- }
- #endregion
- }
|