| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786 |
- 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 string DepartureTerminal { get; set; }
- /// <summary>
- /// 抵达航站楼
- /// </summary>
- public string ArrivalTerminal { get; set; }
- /// <summary>
- /// 机型(如:359、321、32A、73H)
- /// </summary>
- public string AircraftType { get; set; }
- /// <summary>
- /// 耗时(如:11H10M、00H55M、01H35M)
- /// </summary>
- public string Duration { 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; } = new RefundRecord();
- }
- /// <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>
- /// 航班号
- /// 例如:CA431
- /// </summary>
- public string FlightNumber { get; set; }
- /// <summary>
- /// 星期
- /// 例如:TU
- /// </summary>
- public string WeekDay { get; set; }
- /// <summary>
- /// 起飞日期
- /// </summary>
- public DateTime DepartureDate { get; set; }
- /// <summary>
- /// 出发机场三字码
- /// 例如:TFU
- /// </summary>
- public string DepartureAirport { get; set; }
- /// <summary>
- /// 到达机场三字码
- /// 例如:FRA
- /// </summary>
- public string ArrivalAirport { get; set; }
- /// <summary>
- /// 起飞时间
- /// 格式:HH:mm
- /// </summary>
- public string DepartureTime { get; set; }
- /// <summary>
- /// 到达时间
- /// 格式:HH:mm
- /// </summary>
- public string ArrivalTime { get; set; }
- /// <summary>
- /// 是否跨天到达
- /// </summary>
- public bool IsNextDayArrival { get; set; }
- /// <summary>
- /// 跨天数量
- /// 例如:
- /// +1 表示次日到达
- /// +2 表示第三天到达
- /// </summary>
- public int NextDayCount { get; set; }
- /// <summary>
- /// 出发航站楼
- /// 例如:T1
- /// </summary>
- public string DepartureTerminal { get; set; }
- /// <summary>
- /// 到达航站楼
- /// 例如:T2
- /// </summary>
- public string ArrivalTerminal { get; set; }
- /// <summary>
- /// 机型代码
- /// 例如:
- /// 359=A350-900
- /// 321=A321
- /// 32A=A320neo
- /// </summary>
- public string AircraftType { get; set; }
- /// <summary>
- /// 飞行时长
- /// 例如:11H10M
- /// </summary>
- public string Duration { get; set; }
- /// <summary>
- /// 原始文本
- /// </summary>
- public string RawText { get; set; }
- }
- /// <summary>
- /// 航班解析器
- /// 支持格式:
- /// 1.CA431 TU27MAY TFUFRA 0135 0645 T1 1 359 11H10M
- /// 2.LH098 TU28MAY FRAMUC 0915 1010 1 2 321 00H55M
- /// 3.LH2440 TH29MAY MUCCPH 1025 1200 2 2 32A 01H35M
- /// 4.CA878 SA31MAY CPHPEK 1905 1000+1 3 T3 359 08H55M
- /// </summary>
- public static class FlightParser
- {
- /// <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>
- public static List<FlightInfo> ParseFlights(string text)
- {
- var result = new List<FlightInfo>();
- if (string.IsNullOrWhiteSpace(text))
- return result;
- // 支持:
- // 1.CA431 ... 2.LH098 ...
- // 或
- // 1.CA431 ...
- // 2.LH098 ...
- 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;
- }
- /// <summary>
- /// 解析单条航班
- /// </summary>
- 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+");
- if (parts.Length < 5)
- return null;
- int idx = 0;
- // 航班号
- flight.FlightNumber = parts[idx++];
- // 日期
- string dateText = parts[idx++];
- flight.WeekDay = dateText[..2];
- // 这里只解析月日
- flight.DepartureDate = ParseMonthDay(dateText);
- // 航线
- string route = parts[idx++];
- if (route.Length >= 6)
- {
- flight.DepartureAirport = route[..3];
- flight.ArrivalAirport = route.Substring(3, 3);
- }
- // 起飞时间
- flight.DepartureTime = FormatTime(parts[idx++]);
- // 到达时间
- string arrivalRaw = parts[idx++];
- if (arrivalRaw.Contains('+'))
- {
- var arr = arrivalRaw.Split('+');
- flight.ArrivalTime = FormatTime(arr[0]);
- flight.IsNextDayArrival = true;
- if (arr.Length > 1)
- {
- flight.NextDayCount = int.Parse(arr[1]);
- }
- }
- else
- {
- flight.ArrivalTime = FormatTime(arrivalRaw);
- }
- // 剩余字段
- var remain = parts.Skip(idx).ToList();
- ParseRemainFields(remain, flight);
- return flight;
- }
- ///// <summary>
- /////
- ///// </summary>
- ///// <param name="remain">
- ///// 例如:
- ///// T1 1 359 11H10M
- ///// 1 2 321 00H55M
- ///// 3 T3 359 08H55M
- ///// </param>
- /////
- /// <summary>
- /// 解析剩余字段
- /// </summary>
- /// <param name="remain">
- /// 例如:
- /// T1 1 359 11H10M
- /// 1 2 321 00H55M
- /// 3 T3 359 08H55M
- /// </param>
- /// <param name="flight"></param>
- private static void ParseRemainFields(
- List<string> remain,
- FlightInfo flight)
- {
- if (remain.Count < 2)
- return;
- // 最后一个字段一般是飞行时长
- if (Regex.IsMatch(remain[^1], @"^\d{2}H\d{2}M$"))
- {
- flight.Duration = remain[^1];
- remain.RemoveAt(remain.Count - 1);
- }
- // 倒数第二个字段一般是机型
- if (remain.Count > 0 &&
- Regex.IsMatch(remain[^1], @"^\d{2,3}[A-Z]?$"))
- {
- flight.AircraftType = remain[^1];
- remain.RemoveAt(remain.Count - 1);
- }
- // 剩余字段解析为航站楼
- if (remain.Count == 1)
- {
- flight.DepartureTerminal =
- NormalizeTerminal(remain[0]);
- }
- else if (remain.Count >= 2)
- {
- flight.DepartureTerminal =
- NormalizeTerminal(remain[0]);
- flight.ArrivalTerminal =
- NormalizeTerminal(remain[1]);
- }
- }
- /// <summary>
- /// 标准化航站楼
- /// </summary>
- /// <param name="terminal">
- /// 原始值:1、2、 T3
- /// </param>
- /// <returns>
- /// T1、T2、T3
- /// </returns>
- 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;
- }
- /// <summary>
- /// 格式化时间
- /// 0135 -> 01:35
- /// 905 -> 09:05
- /// </summary>
- 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;
- }
- /// <summary>
- /// 自动修正跨年
- /// </summary>
- private static void FixYear(
- List<FlightInfo> 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;
- }
- }
- /// <summary>
- /// 解析月日
- /// TU27MAY -> 05-27
- /// </summary>
- 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);
- }
- }
|