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;
}
}
}