using AutoMapper; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NPOI.OpenXmlFormats.Spreadsheet; using OASystem.Domain; using OASystem.Domain.AesEncryption; using OASystem.Domain.Common; using OASystem.Domain.Dtos.Groups; using OASystem.Domain.Entities.Customer; using OASystem.Domain.Entities.Financial; using OASystem.Domain.Entities.Groups; using OASystem.Domain.Entities.Resource; using OASystem.Domain.ViewModels.Groups; using OASystem.Domain.ViewModels.JuHeExchangeRate; using StackExchange.Redis; using System.Security.Cryptography; namespace OASystem.Infrastructure.Repositories.Groups; public class AirTicketResRepository : BaseRepository { private readonly IMapper _mapper; private readonly DelegationInfoRepository _groupInfoRep; private readonly TeamRateRepository _groupRateRep; public AirTicketResRepository(SqlSugarClient sqlSugar, DelegationInfoRepository groupInfoRep, TeamRateRepository groupRateRep, IMapper mapper) : base(sqlSugar) { _mapper = mapper; _groupInfoRep = groupInfoRep; _groupRateRep = groupRateRep; } #region 旧版 public async Task AirTicketResById(AirTicketResByIdDto dto) { var result = new Result() { Code = -2, Msg = "未知错误" }; try { Grp_AirTicketReservations grp_AirTicket = _sqlSugar.Queryable().First(a => a.Id == dto.Id && a.IsDel == 0); if (grp_AirTicket == null) { return result = new Result() { Code = -1, Msg = "暂无数据" }; } else { Grp_CreditCardPayment grp_CreditCard = _sqlSugar.Queryable().First(a => a.CId == grp_AirTicket.Id && a.IsDel == 0 && a.CTable == 85); if (grp_CreditCard == null) { return result = new Result() { Code = -1, Msg = "暂无数据" }; } return result = new Result() { Code = 0, Msg = "查询成功!", Data = new { AirTicket = grp_AirTicket, CreditCard = grp_CreditCard, } }; } } catch (Exception) { return result = new Result() { Code = -2, Msg = "未知错误" }; } } public async Task AirTicketResList(AirTicketResDto dto) { var result = new Result() { Code = -2, Msg = "未知错误" }; Grp_DelegationInfo _DelegationInfo = _sqlSugar.Queryable().First(it => it.Id == dto.DiId); if (_DelegationInfo != null) { string UserId = ""; List gtaUIdList = _sqlSugar.Queryable().Where(a => a.DIId == dto.DiId && a.IsDel == 0 && a.CTId == 85).ToList(); foreach (Grp_GroupsTaskAssignment gta in gtaUIdList) UserId += gta.UId + ","; if (!string.IsNullOrWhiteSpace(UserId)) { UserId = UserId[..^1]; } else { UserId = "0"; } string sqlWhere = ""; if (dto.IsPaySign != -1) { sqlWhere += string.Format(@" And ccp.IsPay = {0} ", dto.IsPaySign); } string sql = string.Format(@"Select atr.*,ccp.IsAuditGM,sd1.Name As CTypeName,sd2.Name As PreCurrencyStr,sd3.Name As CurrencyStr,ccp.IsPay From Grp_AirTicketReservations atr Left Join Grp_CreditCardPayment ccp On atr.Id = ccp.CId And atr.DIId = ccp.DIId And ccp.IsDel = 0 Left Join Sys_SetData sd1 On sd1.Id = atr.ctype Left Join Sys_SetData sd2 On sd2.Id = atr.PreCurrency Left Join Sys_SetData sd3 On sd3.Id = atr.Currency Where atr.IsDel = 0 And atr.DIId={0} {1} Order By atr.FlightsDate Asc", dto.DiId, sqlWhere); List _AirTicketReservations = _sqlSugar.SqlQueryable(sql).ToList(); foreach (var item in _AirTicketReservations) { if (item.FlightsDescription.Contains("\n")) { var spilitArr = Regex.Split(item.FlightsDescription, "\n"); int rowindex = 1; foreach (var spilitItem in spilitArr) { try { var spDotandEmpty = spilitItem.Split('.')[1].Split(' ').Where(x => !string.IsNullOrEmpty(x)).ToList(); var depCode = spDotandEmpty[3].Substring(0, 3); var arrCode = spDotandEmpty[3].Substring(3, 3); Res_ThreeCode depData = _sqlSugar.Queryable().First(it => it.IsDel == 0 && it.Three == depCode); Res_ThreeCode arrData = _sqlSugar.Queryable().First(it => it.IsDel == 0 && it.Three == arrCode); string day = spDotandEmpty[2].Substring(2, 2);//日 string monthAbbreviations = spDotandEmpty[2].Substring(4, 3).ToUpper();//月份 switch (monthAbbreviations) { case "JAN": monthAbbreviations = "1"; break; case "FEB": monthAbbreviations = "2"; break; case "MAR": monthAbbreviations = "3"; break; case "APR": monthAbbreviations = "4"; break; case "MAY": monthAbbreviations = "5"; break; case "JUN": monthAbbreviations = "6"; break; case "JUL": monthAbbreviations = "7"; break; case "AUG": monthAbbreviations = "8"; break; case "SEP": monthAbbreviations = "9"; break; case "OCT": monthAbbreviations = "10"; break; case "NOV": monthAbbreviations = "11"; break; case "DEC": monthAbbreviations = "12"; break; } string tate = $"{monthAbbreviations}月{day}日"; item.FlightDescription += rowindex + ". " + depData.AirPort + " " + arrData.AirPort + " (" + tate + ")\r\n"; } catch (Exception ex) { item.FlightDescription = "录入数据不规范!请检查"; break; } rowindex++; } } else { try { var spDotandEmpty = item.FlightsDescription.Split('.')[1].Split(' ').Where(x => !string.IsNullOrEmpty(x)).ToList(); var depCode = spDotandEmpty[3].Substring(0, 3); var arrCode = spDotandEmpty[3].Substring(3, 3); Res_ThreeCode depData = _sqlSugar.Queryable().First(it => it.IsDel == 0 && it.Three == depCode); Res_ThreeCode arrData = _sqlSugar.Queryable().First(it => it.IsDel == 0 && it.Three == arrCode); string day = spDotandEmpty[2].Substring(2, 2);//日 string monthAbbreviations = spDotandEmpty[2].Substring(4, 3).ToUpper();//月份 switch (monthAbbreviations) { case "JAN": monthAbbreviations = "1"; break; case "FEB": monthAbbreviations = "2"; break; case "MAR": monthAbbreviations = "3"; break; case "APR": monthAbbreviations = "4"; break; case "MAY": monthAbbreviations = "5"; break; case "JUN": monthAbbreviations = "6"; break; case "JUL": monthAbbreviations = "7"; break; case "AUG": monthAbbreviations = "8"; break; case "SEP": monthAbbreviations = "9"; break; case "OCT": monthAbbreviations = "10"; break; case "NOV": monthAbbreviations = "11"; break; case "DEC": monthAbbreviations = "12"; break; } string tate = $"{monthAbbreviations}月{day}日"; item.FlightDescription += depData.AirPort + " " + arrData.AirPort + " (" + tate + ")\r\n"; } catch (Exception) { item.FlightDescription = "录入数据不规范!请检查"; } } } //团组成本预算表查询 Grp_GroupCostParameter _GroupCostParameter = _sqlSugar.Queryable().First(a => a.DiId == dto.DiId && a.IsDel == 0 && a.CostType == "A" && a.IsShare == 1); AirGroupCostParameterView _AirgroupCostParameter = _mapper.Map(_GroupCostParameter); for (int i = 0; i < _AirTicketReservations.Count; i++) { string[] ClientArr = _AirTicketReservations[i].ClientName.Split(',').Where(x => !string.IsNullOrWhiteSpace(x)).ToArray(); foreach (var item in ClientArr) { bool isNumeric = int.TryParse(item, out _); if (isNumeric) { if (item == "-1") { _AirTicketReservations[i].ClientNameStr += "行程单,"; } else { Crm_DeleClient DeleClient = _sqlSugar.Queryable().Where(a => a.IsDel == 0 && a.Id == int.Parse(item)).First(); EncryptionProcessor.DecryptProperties(DeleClient); _AirTicketReservations[i].ClientNameStr += DeleClient.Pinyin + ','; } } else { _AirTicketReservations[i].ClientNameStr += item; } } if (_AirTicketReservations[i].ClientNameStr is not null) { _AirTicketReservations[i].ClientNameStr = _AirTicketReservations[i].ClientNameStr.Substring(0, _AirTicketReservations[i].ClientNameStr.Length - 1); } } if (dto.PortType == 1) { var data = new { DelegationInfo = _DelegationInfo, AirTicketReservations = _AirTicketReservations, AirGroupCostParameter = _AirgroupCostParameter }; return result = new Result() { Code = 0, Msg = "查询成功!", Data = data }; } else if (dto.PortType == 2 || dto.PortType == 3) { int count = _AirTicketReservations.Count; float totalPage = (float)count / dto.PageSize;//总页数 if (totalPage == 0) totalPage = 1; else totalPage = (int)Math.Ceiling((double)totalPage); var grp_AirTickets = new List(); for (int i = 0; i < dto.PageSize; i++) { var RowIndex = i + (dto.PageIndex - 1) * dto.PageSize; if (RowIndex < _AirTicketReservations.Count) { grp_AirTickets.Add(_AirTicketReservations[RowIndex]); } else { break; } } var rst = new ListViewBase { DataList = grp_AirTickets, DataCount = count, CurrPageIndex = dto.PageIndex, CurrPageSize = dto.PageSize }; var data = new { AirData = rst, AirGroupCostParameter = _AirgroupCostParameter }; return new Result() { Code = 0, Msg = "查询成功!", Data = data }; } else { return new Result() { Code = -1, Msg = "请传入PortType参数!1:Web,2:Android,3:IOS!" }; } } else { return new Result() { Code = -1, Msg = "暂无团组数据!" }; } } public async Task AirTicketResSelect(AirTicketResDto dto) { var result = new Result() { Code = -2, Msg = "未知错误" }; try { if (dto.DiId < 1) { dto.DiId = -1; } #region 团组下拉框 List grp_GroupsTaskAssignment = Query(a => a.IsDel == 0 && a.UId == dto.UserId && a.CTId == 85).ToList(); var grp_NameView = new List(); string DiId = ""; foreach (var item in grp_GroupsTaskAssignment) { DiId += item.DIId + ","; } if (DiId.Length > 1) { DiId = DiId.Substring(0, DiId.Length - 1); string sql = string.Format(@"select * from Grp_DelegationInfo where Id in({0}) and IsDel={1} Order By VisitStartDate Desc", DiId, 0); List grp_Delegations = _sqlSugar.SqlQueryable(sql).ToList(); if (grp_Delegations.Count == 0) { return result = new Result() { Code = -1, Msg = "查询失败!" }; } foreach (var item in grp_Delegations) { var groupNameView = new { item.Id, item.TeamName }; grp_NameView.Add(groupNameView); } } #endregion #region 其他下拉框查询 //舱位类型 List TicketClass = _sqlSugar.Queryable().Where(a => a.STid == 44 && a.IsDel == 0).ToList(); List _TicketClassa = _mapper.Map>(TicketClass); //支付方式 List Payment = _sqlSugar.Queryable().Where(a => a.STid == 14 && a.IsDel == 0).ToList(); List _Payment = _mapper.Map>(Payment); //卡类型 List CardType = _sqlSugar.Queryable().Where(a => a.STid == 15 && a.IsDel == 0).ToList(); List _CardType = _mapper.Map>(CardType); //合作方资料 List _AirTicketAgents = _sqlSugar.Queryable().Where(a => a.IsDel == 0).ToList(); foreach (var item in _AirTicketAgents) { EncryptionProcessor.DecryptProperties(item); } #endregion #region 客人名单下拉框 if (dto.DiId == -1) { var dele = _AirTicketAgents.FirstOrDefault(); dto.DiId = dele != null ? dele.Id : dto.DiId; } string sqlClient = string.Format("select b.Id,b.Pinyin,b.lastName,b.firstName,b.phone from Grp_TourClientList a, Crm_DeleClient b where a.clientid = b.id and a.isdel = 0 and a.diid = {0}", dto.DiId); var clientArr = _sqlSugar.SqlQueryable(sqlClient).ToList(); foreach (var item in clientArr) { EncryptionProcessor.DecryptProperties(item); } #endregion var data = new { TicketClass = _TicketClassa, Payment = _Payment, CardType = _CardType, GroupName = grp_NameView, AirTicketAgents = _AirTicketAgents, clientArr }; return result = new Result() { Code = 0, Msg = "查询成功!", Data = data }; } catch (Exception ex) { return result = new Result() { Code = -2, Msg = "程序错误" }; } } public async Task OpAirTicketRes(AirTicketResOpDto dto, Func> fn) { var result = new Result() { Code = -2, Msg = "未知错误" }; try { BeginTran(); int id = 0; var grp_AirTicket = _mapper.Map(dto.AirTicketResOpData); var clientName = string.Join(",", dto.AirTicketResOpData.ClientName); if (dto.Status == 1) { grp_AirTicket.FlightsDate = DateTime.Parse(grp_AirTicket.FlightsDate).ToString("yyyy-MM-dd"); grp_AirTicket.ClientName = clientName; id = await AddAsyncReturnId(grp_AirTicket); if (id == 0) { result = new Result() { Code = -1, Msg = "添加失败!" }; } else { result = new Result() { Code = 0, Msg = "添加成功!" }; } // } if (result.Code == 0) { Grp_CreditCardPayment grp_CreditCard = _mapper.Map(dto.CardPaymentOpData); // 2025-04-07 第四次更改 PayDId == 72(刷卡) IsPay == 1 if (grp_CreditCard.PayDId == 72) grp_CreditCard.IsPay = 1; else grp_CreditCard.IsPay = 0; //获取新汇率 int diId,int CId, int currencyId var rate = await fn(dto.AirTicketResOpData.DiId, 85, dto.AirTicketResOpData.Currency); if (rate.Code == 0) { var rateInfo = (rate.Data as CurrencyInfo); if (rateInfo is not null) { grp_CreditCard.DayRate = rateInfo.Rate; grp_CreditCard.RMBPrice = rateInfo.Rate * grp_CreditCard.PayMoney; } else { grp_CreditCard.DayRate = 1; grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney; } } //换算 #region 老汇率处理方式 //Grp_TeamRate _TeamRate = _sqlSugar.Queryable().First(a => a.DiId == grp_AirTicket.DIId && a.IsDel == 0 && a.CTable == 85); //if (_TeamRate != null) //{ // if (grp_CreditCard.PaymentCurrency == 49) // { // grp_CreditCard.DayRate = _TeamRate.RateU; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney * Convert.ToDecimal(_TeamRate.RateU); // //ccp.PayMoney = ccp.PayMoney * float.Parse(tr.RateU); // } // else if (grp_CreditCard.PaymentCurrency == 51) // { // grp_CreditCard.DayRate = _TeamRate.RateE; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney * Convert.ToDecimal(_TeamRate.RateE); // //ccp.PayMoney = ccp.PayMoney * float.Parse(tr.RateE); // } // else // { // grp_CreditCard.DayRate = 1M; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney; // } //} //else //{ // grp_CreditCard.DayRate = 1M; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney; //} #endregion //判断是否超出成本 #region 自动审核(暂时屏蔽) //Grp_GroupCostParameter _GroupCostParameter = _sqlSugar.Queryable().First(a => a.DiId == grp_AirTicket.DIId && a.IsDel == 0); //if (grp_AirTicket.CType == 460)//经济舱 //{ // if (_GroupCostParameter != null) // { // if (Convert.ToDecimal(_GroupCostParameter.JJCCB) * Convert.ToDecimal(grp_AirTicket.ClientNum) > grp_CreditCard.RMBPrice) // { // grp_CreditCard.ExceedBudget = 0;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 3;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 0; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } // else // { // var a = grp_CreditCard.RMBPrice - Convert.ToDecimal(_GroupCostParameter.JJCCB); // var b = a / Convert.ToDecimal(_GroupCostParameter.JJCCB) * Convert.ToDecimal(grp_AirTicket.ClientNum); // grp_CreditCard.ExceedBudget = b;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } // } // else // { // grp_CreditCard.ExceedBudget = 0.00M;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 0; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } //} //else if (grp_AirTicket.CType == 458) //{ // if (_GroupCostParameter != null) // { // if (Convert.ToDecimal(_GroupCostParameter.GWCB) * Convert.ToDecimal(grp_AirTicket.ClientNum) > grp_CreditCard.RMBPrice) // { // grp_CreditCard.ExceedBudget = 0;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 3;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } // else // { // var a = grp_CreditCard.RMBPrice - Convert.ToDecimal(_GroupCostParameter.GWCB); // var b = a / Convert.ToDecimal(_GroupCostParameter.GWCB) * Convert.ToDecimal(grp_AirTicket.ClientNum); // grp_CreditCard.ExceedBudget = b;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } // } // else // { // grp_CreditCard.ExceedBudget = 0.00M;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } //} //else //{ // grp_CreditCard.ExceedBudget = 0.00M;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 0; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; //} #endregion grp_CreditCard.CId = id; grp_CreditCard.CTable = 85; grp_CreditCard.PayPercentage = 100; #region 查询上一次付款(注释) ////查询上一次付款信息 //Grp_CreditCardPayment _CreditCardPayment = _sqlSugar.Queryable().OrderByDescending(x => x.CreateUserId).First(a => a.DIId == grp_AirTicket.DIId && a.IsDel == 0 && a.IsPay == 1); //if (_CreditCardPayment != null) //{ // grp_CreditCard.PayPercentageOld = _CreditCardPayment.PayPercentageOld;// 上次付款百分比 查询并计算最近一次 // grp_CreditCard.PayThenMoneyOld = _CreditCardPayment.PayMoney;// 上次付款金额 查询上一次 // grp_CreditCard.UpdateDate = _CreditCardPayment.UpdateDate;// 上次付款时间 查询上一次 //} //else //{ // grp_CreditCard.PayPercentageOld = 0;// 上次付款百分比 查询并计算最近一次 // grp_CreditCard.PayThenMoneyOld = 0;// 上次付款金额 查询上一次 // grp_CreditCard.UpdateDate = "";// 上次付款时间 查询上一次 //} #endregion id = await _sqlSugar.Insertable(grp_CreditCard).ExecuteReturnIdentityAsync(); if (id == 0) { RollbackTran(); return result = new Result() { Code = -1, Msg = "添加失败!" }; } await UpdateAsync(a => a.Id == id, a => new Grp_AirTicketReservations { FlightsDescription = dto.AirTicketResOpData.FlightsDescription, }); CommitTran(); var data = new { ccpId = id, sign = 1, dataId = grp_CreditCard.CId }; return result = new Result() { Code = 0, Msg = "添加成功!", Data = data }; } else { RollbackTran(); return result = new Result() { Code = -1, Msg = "添加失败!" }; } } else if (dto.Status == 2) { id = dto.AirTicketResOpData.Id; bool res = await UpdateAsync(a => a.Id == dto.AirTicketResOpData.Id, a => new Grp_AirTicketReservations { CType = dto.AirTicketResOpData.CType, PrePrice = dto.AirTicketResOpData.PrePrice, PreCurrency = dto.AirTicketResOpData.PreCurrency, Price = dto.AirTicketResOpData.Price, Currency = dto.AirTicketResOpData.Currency, ClientNum = dto.AirTicketResOpData.ClientNum, ClientName = clientName, IsCheckIn = dto.AirTicketResOpData.IsCheckIn, IsSetSeat = dto.AirTicketResOpData.IsSetSeat, IsPackage = dto.AirTicketResOpData.IsPackage, IsBagHandle = dto.AirTicketResOpData.IsBagHandle, IsTrain = dto.AirTicketResOpData.IsTrain, FlightsCode = dto.AirTicketResOpData.FlightsCode, FlightsCity = dto.AirTicketResOpData.FlightsCity, FlightsDescription = dto.AirTicketResOpData.FlightsDescription, PriceDescription = dto.AirTicketResOpData.PriceDescription, TicketNumber = dto.AirTicketResOpData.TicketNumber, TicketCode = dto.AirTicketResOpData.TicketCode, Remark = dto.AirTicketResOpData.Remark, }); if (!res) { result = new Result() { Code = -1, Msg = "修改失败!" }; } else { result = new Result() { Code = 0, Msg = "修改成功!" }; } if (result.Code == 0) { //C表操作 Grp_CreditCardPayment grp_CreditCard = _mapper.Map(dto.CardPaymentOpData); //2025-04-07 第四次更改 PayDId == 72(刷卡) IsPay == 1 if (grp_CreditCard.PayDId == 72) grp_CreditCard.IsPay = 1; else grp_CreditCard.IsPay = 0; //获取新汇率 int diId,int CId, int currencyId var rate = await fn(dto.AirTicketResOpData.DiId, 85, dto.AirTicketResOpData.Currency); if (rate.Code == 0) { var rateInfo = (rate.Data as CurrencyInfo); if (rateInfo is not null) { grp_CreditCard.DayRate = rateInfo.Rate; grp_CreditCard.RMBPrice = rateInfo.Rate * grp_CreditCard.PayMoney; } else { grp_CreditCard.DayRate = 1; grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney; } } #region 老汇率修改(注释) //换算 Grp_TeamRate _TeamRate = _sqlSugar.Queryable().First(a => a.DiId == grp_AirTicket.DIId && a.IsDel == 0 && a.CTable == 85); //if (_TeamRate != null) //{ // if (grp_CreditCard.PaymentCurrency == 49) // { // grp_CreditCard.DayRate = _TeamRate.RateU; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney * Convert.ToDecimal(_TeamRate.RateU); // //ccp.PayMoney = ccp.PayMoney * float.Parse(tr.RateU); // } // else if (grp_CreditCard.PaymentCurrency == 51) // { // grp_CreditCard.DayRate = _TeamRate.RateE; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney * Convert.ToDecimal(_TeamRate.RateE); // //ccp.PayMoney = ccp.PayMoney * float.Parse(tr.RateE); // } // else // { // grp_CreditCard.DayRate = 1M; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney; // } //} //else //{ // grp_CreditCard.DayRate = 1M; // grp_CreditCard.RMBPrice = grp_CreditCard.PayMoney; //} #endregion #region 自动审核(注释) //Grp_GroupCostParameter _GroupCostParameter = _sqlSugar.Queryable().First(a => a.DiId == grp_AirTicket.DIId && a.IsDel == 0); //if (grp_AirTicket.CType == 460)//经济舱 //{ // if (_GroupCostParameter != null) // { // if (Convert.ToDecimal(_GroupCostParameter.JJCCB) * Convert.ToDecimal(grp_AirTicket.ClientNum) > grp_CreditCard.RMBPrice) // { // grp_CreditCard.ExceedBudget = 0;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 3;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 0; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 1; // } // else // { // var a = grp_CreditCard.RMBPrice - Convert.ToDecimal(_GroupCostParameter.JJCCB); // var b = a / Convert.ToDecimal(_GroupCostParameter.JJCCB) * Convert.ToDecimal(grp_AirTicket.ClientNum); // grp_CreditCard.ExceedBudget = b;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 1; // } // } // else // { // grp_CreditCard.ExceedBudget = 0.00M;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } //} //else if (grp_AirTicket.CType == 458) //{ // if (_GroupCostParameter != null) // { // if ((_GroupCostParameter != null ? _GroupCostParameter.GWCB : 0) * Convert.ToDecimal(grp_AirTicket.ClientNum) > grp_CreditCard.RMBPrice) // { // grp_CreditCard.ExceedBudget = 0;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 3;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 1; // } // else // { // var a = grp_CreditCard.RMBPrice - Convert.ToDecimal(_GroupCostParameter.GWCB); // var b = a / Convert.ToDecimal(_GroupCostParameter.GWCB) * Convert.ToDecimal(grp_AirTicket.ClientNum); // grp_CreditCard.ExceedBudget = b;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 1; // } // } // else // { // grp_CreditCard.ExceedBudget = 0.00M;// 超出预算比例 换算 // grp_CreditCard.IsAuditGM = 0;//3 为自动审核 // grp_CreditCard.AuditGMOperate = 21; // grp_CreditCard.AuditGMDate = ""; // grp_CreditCard.IsPay = 0; // } //} #endregion grp_CreditCard.CId = id; grp_CreditCard.CTable = 85; grp_CreditCard.PayPercentage = 100; #region 查询上一次付款信息(注释) ////查询上一次付款信息 //Grp_CreditCardPayment _CreditCardPayment = _sqlSugar.Queryable().OrderByDescending(x => x.CreateUserId).First(a => a.DIId == grp_AirTicket.DIId && a.IsDel == 0 && a.IsPay == 1); //if (_CreditCardPayment != null) //{ // grp_CreditCard.PayPercentageOld = _CreditCardPayment.PayPercentageOld;// 上次付款百分比 查询并计算最近一次 // grp_CreditCard.PayThenMoneyOld = _CreditCardPayment.PayMoney;// 上次付款金额 查询上一次 // grp_CreditCard.UpdateDate = _CreditCardPayment.UpdateDate;// 上次付款时间 查询上一次 //} //else //{ // grp_CreditCard.PayPercentageOld = 0;// 上次付款百分比 查询并计算最近一次 // grp_CreditCard.PayThenMoneyOld = 0;// 上次付款金额 查询上一次 // grp_CreditCard.UpdateDate = "";// 上次付款时间 查询上一次 //} #endregion int CTable = await _sqlSugar.Updateable().Where(a => a.Id == dto.CardPaymentOpData.Id).SetColumns(a => new Grp_CreditCardPayment { PayDId = grp_CreditCard.PayDId, ConsumptionPatterns = grp_CreditCard.ConsumptionPatterns, ConsumptionDate = grp_CreditCard.ConsumptionDate, CTDId = grp_CreditCard.CTDId, BankNo = grp_CreditCard.BankNo, CardholderName = grp_CreditCard.CardholderName, PayMoney = grp_CreditCard.PayMoney, PaymentCurrency = grp_CreditCard.PaymentCurrency, DayRate = grp_CreditCard.DayRate, CompanyBankNo = grp_CreditCard.CompanyBankNo, OtherBankName = grp_CreditCard.OtherBankName, OtherSideNo = grp_CreditCard.OtherSideNo, OtherSideName = grp_CreditCard.OtherSideName, IsAuditGM = grp_CreditCard.IsAuditGM, AuditGMOperate = grp_CreditCard.AuditGMOperate, AuditGMDate = grp_CreditCard.AuditGMDate, IsPay = grp_CreditCard.IsPay, PayPercentage = grp_CreditCard.PayPercentage, PayThenMoney = grp_CreditCard.PayThenMoney, PayPercentageOld = grp_CreditCard.PayPercentageOld, PayThenMoneyOld = grp_CreditCard.PayThenMoneyOld, UpdateDate = grp_CreditCard.UpdateDate, Payee = grp_CreditCard.Payee, RMBPrice = grp_CreditCard.RMBPrice, OrbitalPrivateTransfer = grp_CreditCard.OrbitalPrivateTransfer, ExceedBudget = grp_CreditCard.ExceedBudget, Remark = grp_CreditCard.Remark, }).ExecuteCommandAsync(); if (CTable == 0) { RollbackTran(); return result = new Result() { Code = -1, Msg = "修改失败!" }; } else { Grp_CreditCardPayment ccp = Query(a => a.Id == dto.CardPaymentOpData.Id).First(); var data = new { ccpId = ccp.Id, sign = 2, dataId = grp_CreditCard.CId }; result = new Result() { Code = 0, Msg = "修改成功!", Data = data }; } } else { result = new Result() { Code = -1, Msg = "修改失败!" }; RollbackTran(); } } CommitTran(); } catch (Exception ex) { RollbackTran(); return result = new Result() { Code = -2, Msg = "程序错误" }; throw; } return result; } /// /// 导出机票费用报表 /// /// /// /// public async Task DeriveAirTicketRes(AirTicketResDto dto) { var result = new Result() { Code = -2, Msg = "未知错误" }; try { string sql = string.Format(@"Select atr.*,ccp.Payee,ccp.BankNo, sd4.Name as PayType ,ccp.IsAuditGM,sd1.Name As 'CTypeName',sd2.Name As 'PreCurrencyStr',sd3.Name As CurrencyStr From Grp_AirTicketReservations atr Left Join Grp_CreditCardPayment ccp On atr.Id = ccp.CId And atr.DIId = ccp.DIId And ccp.IsDel = 0 Left Join Sys_SetData sd1 On sd1.Id = atr.ctype Left Join Sys_SetData sd2 On sd2.Id = atr.PreCurrency Left Join Sys_SetData sd3 On sd3.Id = atr.Currency left join Sys_SetData sd4 On sd4.Id = ccp.PayDId Where atr.IsDel = 0 And atr.DIId={0} Order By atr.FlightsDate Asc", dto.DiId); List _AirTicketReservations = _sqlSugar.SqlQueryable(sql).ToList(); if (_AirTicketReservations.Count != 0) { Grp_DelegationInfo grp_Delegation = _sqlSugar.Queryable().First(a => a.Id == dto.DiId && a.IsDel == 0); Sys_Users _Users = _sqlSugar.Queryable().First(a => a.Id == dto.UserId && a.IsDel == 0); return result = new Result() { Code = 0, Msg = "成功", Data = new { Delegation = grp_Delegation, AirTicketRes = _AirTicketReservations, Users = _Users } }; } else { return result = new Result() { Code = -1, Msg = "暂无数据", Data = null }; } } catch (Exception) { return result = new Result() { Code = -2, Msg = "未知错误" }; } } public async Task ItineraryAirTicketRes(ItineraryAirTicketResDto dto) { try { string sql = string.Format(@"select a.*,c.IsAuditGM,(select Name from Sys_SetData where Id=a.cType) as 'CTypeName',(select Name from Sys_SetData where Id=a.PreCurrency) as 'PreCurrencyStr',(select Name from Sys_SetData where Id=a.Currency) as 'CurrencyStr' from Grp_AirTicketReservations a,Grp_CreditCardPayment c where a.id=c.CId and a.isdel={1} and a.DIId={0} Order By a.CreateTime desc", dto.DiId, 0); List _AirTicketReservations = _sqlSugar.SqlQueryable(sql).ToList(); if (_AirTicketReservations.Count == 0) { return new Result() { Code = -1, Msg = "暂无数据", Data = null }; } else { return new Result() { Code = 0, Msg = "查询成功", Data = _AirTicketReservations }; } } catch (Exception) { return new Result() { Code = -2, Msg = "未知错误" }; } } public async Task> ReTransBatch(List list, string aimlanguage) { var reultStr = new List(); if (list.Count < 0) { return reultStr; } //待翻译内容,必须是UTF-8编码 string q = ""; string Qtext = ""; foreach (var item in list) { q += item; Qtext += "q=" + item + "&"; } if (Qtext.Length > 0) { Qtext = Qtext[..^1]; } var dic = new Dictionary(); string url = "https://openapi.youdao.com/v2/api"; //应用ID string appKey = "0fe3bc01e109ed36"; //应用应用密钥 string appSecret = "1f2x9TrqJfSBEJ8iH9GEFGgTyaYGjEry"; //UUID string salt = DateTime.Now.Millisecond.ToString(); //源语言 dic.Add("from", "zh-CHS"); //目标语言 dic.Add("to", aimlanguage); //签名类型 dic.Add("signType", "v3"); //时间戳 string curtime = ((GetBeijingTime().ToUniversalTime().Ticks - 621355968000000000) / 10000000).ToString(); dic.Add("curtime", curtime); string signStr = appKey + Truncate(q) + salt + curtime + appSecret; ; string sign = ComputeHash(signStr, SHA256.Create()); dic.Add("q", Qtext); dic.Add("appKey", appKey); dic.Add("salt", salt); dic.Add("sign", sign); try { //Thread.Sleep(500); string jsonStr = await TransSync(url, dic); JObject trans = (JObject)JsonConvert.DeserializeObject(jsonStr); string errorCode = trans["errorCode"].ToString(); if (errorCode == "0") { reultStr = JsonConvert.DeserializeObject>(trans["translateResults"].ToString()); } return reultStr; } catch (Exception) { return reultStr; } } public string Processing(string str)//处理这段英文的方法 { if (string.IsNullOrEmpty(str)) { return ""; } string[] strArray; if (str.Contains(" ")) strArray = str.Split(" ".ToCharArray()); else if (str.Contains(",")) strArray = str.Split(",".ToCharArray()); else if (str.Contains(" ") && str.Contains(",")) strArray = str.Split(",".ToCharArray()); else strArray = new string[] { str }; string result = string.Empty;//定义一个空字符串 foreach (string s in strArray)//循环处理数组里面每一个字符串 { //result += System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(s) + " "; result += string.Concat(s[..1].ToUpper(), s.AsSpan(1), " "); //.Substring(0, 1).ToUpper()把循环到的字符串第一个字母截取并转换为大写,并用s.Substring(1)得到循环到的字符串除第一个字符后的所有字符拼装到首字母后面。 } return result; } protected static async Task TransSync(string url, Dictionary dic) { string result = ""; using (var client = new HttpClient()) { var content = new FormUrlEncodedContent(dic); HttpResponseMessage response = await client.PostAsync(url, content); response.EnsureSuccessStatusCode(); if (response.Content.Headers.ContentType.MediaType.ToLower().Equals("audio/mp3")) { await SaveBinaryFile(response, "合成的音频存储路径"); } else { result = await response.Content.ReadAsStringAsync(); } } return result; } private static async Task SaveBinaryFile(HttpResponseMessage response, string FileName) { string FilePath = FileName + DateTime.Now.Millisecond.ToString() + ".mp3"; bool Value = true; byte[] buffer = new byte[1024]; try { if (File.Exists(FilePath)) File.Delete(FilePath); using Stream outStream = File.Create(FilePath); using Stream inStream = await response.Content.ReadAsStreamAsync(); int l; do { l = await inStream.ReadAsync(buffer); if (l > 0) await outStream.WriteAsync(buffer.AsMemory(0, l)); } while (l > 0); } catch { Value = false; } return Value; } protected static string ComputeHash(string input, HashAlgorithm algorithm) { byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashedBytes = algorithm.ComputeHash(inputBytes); return BitConverter.ToString(hashedBytes).Replace("-", ""); } protected static string Truncate(string q) { if (q == null) { return null; } int len = q.Length; return len <= 20 ? q : q.Substring(0, 10) + len + q.Substring(len - 10, 10); } public static DateTime GetBeijingTime() { string datetime = string.Empty; try { using (var client = new HttpClient()) { client.Timeout = TimeSpan.FromSeconds(3); HttpResponseMessage response = client.GetAsync("https://www.baidu.com").Result; response.EnsureSuccessStatusCode(); datetime = response.Headers.Date?.ToString() ?? DateTime.Now.ToString(); } return Convert.ToDateTime(datetime); } catch (Exception) { return DateTime.Now; } } public class TranslateResult { public string Query { get; set; } public string Translation { get; set; } public string Type { get; set; } public string VerifyResult { get; set; } } #endregion #region 2026版 /// /// list 2026版 /// /// 查询参数 /// 机票费用列表 public async Task ListAsync(AirTicketFeeListDto dto) { // 1. 参数校验 if (!SharingStaticData.PortTypes.Contains(dto.PortType)) return JsonView.Fail(MsgTips.Port); if (dto.DiId <= 0) return JsonView.Fail("团组ID无效"); // 2. 获取团组信息 var groupInfo = await _groupInfoRep.GetGroupAirInfo(dto.DiId); if (groupInfo == null) return JsonView.Fail("暂无团组数据!"); // 3. 查询机票费用列表 var airTicketFeeList = await _sqlSugar.Queryable() .InnerJoin((x, y) => x.Id == y.CId && x.DIId == y.DIId) .LeftJoin((x, y, z) => x.CType == z.Id) .LeftJoin((x, y, z, z1) => x.Currency == z1.Id) .LeftJoin((x, y, z, z1, u) => x.CreateUserId == u.Id) .Where((x, y, z, z1, u) => x.IsDel == 0 && y.IsDel == 0 && x.DIId == dto.DiId) .WhereIF(dto.IsPaySign != -1, (x, y, z, z1, u) => y.IsPay == dto.IsPaySign) .Select((x, y, z, z1, u) => new AirTicketFeeListView() { Id = x.Id, No = x.RecordType, CabinName = z.Name, FlightDesc = x.FlightsDescription, ClientNameList = x.ClientName, ClientCount = x.ClientNum, TotalTicketPrice = x.Price, Currency = z1.Name, AuditStatus = y.IsAuditGM.ToString(), Remark = y.Remark, Applicant = u.CnName, ApplyTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") }) .ToListAsync(); // 4. 获取黑屏代码(三字码映射) var threeCodeList = await _sqlSugar.Queryable() .Where(x => x.IsDel == 0) .Select(x => new { x.Three, x.AirPort }) .ToListAsync(); // 保留第一个遇到的 var threeCodes = threeCodeList .GroupBy(x => x.Three) .ToDictionary(g => g.Key, g => g.First().AirPort); // 5. 获取团组成本预算 var groupCostParameter = await _sqlSugar.Queryable() .FirstAsync(a => a.DiId == dto.DiId && a.IsDel == 0 && a.CostType == "A" && a.IsShare == 1); var airgroupCostParameter = _mapper.Map(groupCostParameter); // 6. 获取客户信息映射 var groupClientIds = await _sqlSugar.Queryable() .Where(a => a.DiId == dto.DiId && a.IsDel == 0) .Select(x => x.ClientId) .ToListAsync(); var clientInfoMap = new Dictionary(); if (groupClientIds.Any()) { var encryptedClients = await _sqlSugar.Queryable() .Where(a => a.IsDel == 0 && groupClientIds.Contains(a.Id)) .Select(x => new { x.Id, x.FirstName, x.LastName, x.Pinyin }) .ToListAsync(); foreach (var client in encryptedClients) { clientInfoMap[client.Id] = ( AesEncryptionHelper.Decrypt(client.FirstName), AesEncryptionHelper.Decrypt(client.LastName), AesEncryptionHelper.Decrypt(client.Pinyin) ); } } // 7. 处理航班描述和客户名称 foreach (var item in airTicketFeeList) { // 退票退票标识 if (item.No == 1) { item.CabinName += "(退票)"; } // 7.1 处理航班描述 if (!string.IsNullOrWhiteSpace(item.FlightDesc)) { var flightInfos = FlightParser.ParseFlights(item.FlightDesc); var flightDescBuilder = new StringBuilder(); foreach (var flightInfo in flightInfos) { string departAirport = threeCodes.ContainsKey(flightInfo.DepartureAirport) ? threeCodes[flightInfo.DepartureAirport]?.ToString() ?? flightInfo.DepartureAirport : flightInfo.DepartureAirport; string arrivalAirport = threeCodes.ContainsKey(flightInfo.ArrivalAirport) ? threeCodes[flightInfo.ArrivalAirport]?.ToString() ?? flightInfo.ArrivalAirport : flightInfo.ArrivalAirport; string desc = $"{flightInfo.SequenceNo}.{departAirport} → {arrivalAirport}({flightInfo.DepartureDate:yyyy-MM-dd})"; flightDescBuilder.AppendLine(desc); } item.FlightDesc = flightDescBuilder.ToString().TrimEnd(); } else item.FlightDesc = "录入数据不规范!请检查"; // 7.2 处理客户名称 if (!string.IsNullOrWhiteSpace(item.ClientNameList)) { var clientArr = item.ClientNameList.Split(',') .Where(x => !string.IsNullOrWhiteSpace(x)) .ToArray(); var clientNames = new List(); foreach (var clientItem in clientArr) { if (int.TryParse(clientItem, out int clientId)) { if (clientId == -1) { clientNames.Add("行程单"); } else if (clientInfoMap.TryGetValue(clientId, out var clientInfo)) { clientNames.Add(clientInfo.Pinyin); } } else { clientNames.Add(clientItem); } } item.ClientNameList = clientNames.Any() ? string.Join(",", clientNames) : string.Empty; } } // 8. 根据端口类型返回数据 if (dto.PortType == 1) { var data = new { DelegationInfo = groupInfo, AirTicketReservations = airTicketFeeList, AirGroupCostParameter = airgroupCostParameter }; return JsonView.Success(data); } else if (dto.PortType == 2 || dto.PortType == 3) { int totalCount = airTicketFeeList.Count; int totalPages = (int)Math.Ceiling((double)totalCount / dto.PageSize); if (totalPages == 0) totalPages = 1; // 分页数据 var pagedData = airTicketFeeList .Skip((dto.PageIndex - 1) * dto.PageSize) .Take(dto.PageSize) .ToList(); var rst = new ListViewBase { DataList = pagedData, DataCount = totalCount, CurrPageIndex = dto.PageIndex, CurrPageSize = dto.PageSize }; var data = new { AirData = rst, AirGroupCostParameter = airgroupCostParameter }; return JsonView.Success(data); } return JsonView.Fail("暂无团组数据!"); } /// /// 机票费用基础数据 2026版 /// /// public async Task BasicDataAsync(int userId) { var setDatas = _sqlSugar.Queryable().Where(a => a.IsDel == 0).ToList(); // 附加服务类型 var additionalServiceTypes = setDatas .Where(a => a.STid == 139 && a.IsDel == 0) .Select(a => _mapper.Map(a)) .OrderBy(x => x.RemarkSort) .ToList(); // 舱位类型 var ticketClass = setDatas .Where(a => a.STid == 44 && a.IsDel == 0) .Select(a => _mapper.Map(a)) .ToList(); // 支付方式 var payments = setDatas .Where(a => a.STid == 14 && a.IsDel == 0) .Select(a => _mapper.Map(a)) .ToList(); // 卡类型 var cardTypes = setDatas .Where(a => a.STid == 15 && a.IsDel == 0) .Select(a => _mapper.Map(a)) .ToList(); // 合作方资料 var airTicketAgents = _sqlSugar.Queryable().Where(a => a.IsDel == 0).ToList(); foreach (var item in airTicketAgents) { EncryptionProcessor.DecryptProperties(item); } var airTicketAgentsView = airTicketAgents.Select(x => new { x.Id, x.Name, x.Account, x.Bank }).ToList(); #region 团组下拉框 var permGroupIds = Query(a => a.IsDel == 0 && a.UId == userId && a.CTId == 85).Select(a => a.DIId).ToList(); var teamNames = _sqlSugar.Queryable() .Where(x => x.IsDel == 0 && permGroupIds.Contains(x.Id)) .OrderByDescending(x => x.VisitDate) .Select(x => new { x.Id, x.TeamName }) .ToList(); #endregion // 获取第一个团组的基本信息 var firstGroupInfo = await _groupInfoRep.GetGroupAirInfo(teamNames.FirstOrDefault()?.Id ?? 0); return JsonView.Success(new { additionalServiceTypes, ticketClass, payment = payments, cardType = cardTypes, airTicketAgents = airTicketAgentsView, firstGroupInfo, groupName = teamNames }); } /// /// 获取 2026版机票费用详情(支持多航段、多舱位及退票账目精准聚合) /// /// /// 【核心业务与技术逻辑】 /// 1. 全量数据捞取: 放开 RecordType 限制,同时加载正常票(0)与退票(1)记录,确保退票全口径核算。 /// 2. 安全字典映射: 改用 item.CType 进行舱位汉化,彻底杜绝空舱位时因 CustTicketInfos 为空引发的空指针崩溃(NullReference)。 /// 3. 财务明细保护: 针对多舱位对应多付款单场景,将各类卡的审核状态以【舱位(票种):状态】精简串联,账目清晰直观。 /// public async Task InfoAsync(int userId, int groupId) { var view = new AirTicketFeeInfoView() { CurrUserId = userId, GroupAirInfo = new GroupAirTicketInfo(), AirTicketFeeInfos = new List() }; if (userId < 1) return JsonView.Fail("用户ID无效"); if (groupId < 1) return JsonView.Fail("团组ID无效"); // 1. 权限校验 var groupVaild = Query(a => a.IsDel == 0 && a.UId == userId && a.CTId == 85 && a.DIId == groupId).Any(); if (!groupVaild) return JsonView.Fail("没有查看权限"); // 2. 获取团组基本信息 var firstGroupInfo = await _groupInfoRep.GetGroupAirInfo(groupId); if (firstGroupInfo == null) return JsonView.Fail("团组信息不存在"); var groupInfo = _mapper.Map(firstGroupInfo); // 3. 机票全量信息集合(同时包含正常票与退票 records) var dbAirTicketList = await _sqlSugar.Queryable() .InnerJoin((x, y) => x.Id == y.CId && x.DIId == y.DIId && y.CTable == 85) .Where((x, y) => x.IsDel == 0 && y.IsDel == 0 && x.DIId == groupId && x.RecordType == 0) .Select((x, y) => new { Air = x, Card = y }) .ToListAsync(); if (!dbAirTicketList.Any()) { groupInfo.FlightsDescription = string.Empty; return JsonView.Success(new AirTicketFeeInfoView() { CurrUserId = userId, GroupAirInfo = groupInfo, AirTicketFeeInfos = new List() }); } // 4. 加载辅助数据字典 var cabinTypes = await _sqlSugar.Queryable().Where(x => x.IsDel == 0 && x.STid == 44).ToListAsync(); // 5. 按【航段】进行高级业务聚合 var groupedByFlights = dbAirTicketList .GroupBy(x => x.Air.FlightsDescription) .Select(g => { var rawList = g.ToList(); // 直接利用 IEnumerable 接口,避开泛型类型转换限制 var hasValidCabin = rawList.Any(x => x.Air.CType > 0); var filteredSource = hasValidCabin ? rawList.Where(x => x.Air.CType > 0) : rawList; // 对过滤后的数据流进行排序 var sortedGroup = filteredSource .OrderByDescending(x => x.Air.CustTicketInfos != null && x.Air.CustTicketInfos.Any()) .ThenBy(x => x.Air.RecordType) .ToList(); // 锚定当前航段权重最高的记录作为代表单据 var primaryItem = sortedGroup.First(); // 判定整个航段下是否有真实客户 bool hasAnyClient = sortedGroup.Any(x => x.Air.CustTicketInfos != null && x.Air.CustTicketInfos.Any()); // 合并客户姓名(去重) var allClientNames = string.Join(",", sortedGroup.Select(x => x.Air.ClientName).Where(n => !string.IsNullOrEmpty(n)).Distinct()); // 如果没有有效客户,或者是 CType == 0 降级显示的情况,填充默认语 if (!hasAnyClient || primaryItem.Air.CType == 0) { allClientNames = "暂无客户分配(空舱位/未定舱位留存)"; } var clientNameIds = ParseToIntListSafe(allClientNames); var allCustTicketInfos = sortedGroup.SelectMany(x => x.Air.CustTicketInfos ?? new List()).ToList(); // 汇总金额 decimal totalPayMoney = primaryItem?.Air?.CustTicketInfos?.Sum(c => { // 机票票面价 + 附加服务费 decimal ticketAndServices = (c?.ActualPrice ?? 0M) + (c?.AdditionalServices?.Sum(d => d?.Amount ?? 0M) ?? 0M); // 退票相关费用(退票金额通常为负值,退款给客户) decimal refundFees = (c?.RefundRecord?.RefundAmount ?? 0M) + (c?.RefundRecord?.NonRefundableTax ?? 0M); return ticketAndServices + refundFees; }) ?? 0M; // 多舱位状态精炼串联 var auditsStr = new StringBuilder(); foreach (var item in sortedGroup) { var auditStatus = item.Card.IsAuditGM switch { 0 => "未审核", 1 => "已通过", 2 => "已驳回", 3 => "自动审核", _ => "未知" }; var typeDesc = item.Air.RecordType == 1 ? "退票" : "正常"; // 针对 CType == 0 的特殊汉化处理 var cabinTypeName = item.Air.CType == 0 ? "未定舱位" : (cabinTypes.FirstOrDefault(c => c.Id == item.Air.CType)?.Name ?? $"未知舱位({item.Air.CType})"); auditsStr.Append($"{cabinTypeName}({typeDesc}):{auditStatus};"); } return new AirTicketFeeInfo { Id = primaryItem.Air.Id, FlightsDescription = g.Key, AirTicketBasicInfos = primaryItem.Air.AirTicketBasicInfos ?? new List(), ClientName = allClientNames, ClientNameIds = clientNameIds, CustTicketInfos = allCustTicketInfos, PriceDescription = primaryItem.Air.PriceDescription, // 财务信息绑定 PayDId = primaryItem.Card.PayDId, ConsumptionPatterns = primaryItem.Card.ConsumptionPatterns, ConsumptionDate = primaryItem.Card.ConsumptionDate, PayMoney = totalPayMoney, PaymentCurrency = primaryItem.Card.PaymentCurrency, CTDId = primaryItem.Card.CTDId, BankNo = primaryItem.Card.BankNo, CardholderName = primaryItem.Card.CardholderName, DayRate = primaryItem.Card.DayRate, CompanyBankNo = primaryItem.Card.CompanyBankNo, OtherSideNo = primaryItem.Card.OtherSideNo, OtherSideName = primaryItem.Card.OtherSideName, Payee = primaryItem.Card.Payee, OrbitalPrivateTransfer = primaryItem.Card.OrbitalPrivateTransfer, Remark = primaryItem.Card.Remark, IsAuditGM = primaryItem.Card.IsAuditGM, AuditGMOperate = primaryItem.Card.AuditGMOperate, AuditGMDate = primaryItem.Card.AuditGMDate, AuditStr = auditsStr.ToString().TrimEnd(';') }; }) .ToList(); // 6. 排序及全局行程链组装 groupedByFlights = groupedByFlights .OrderBy(x => ExtractSequenceNo(x.FlightsDescription)) .ToList(); var flightsDescAll = string.Join("", groupedByFlights.Select(x => x.FlightsDescription).Distinct()); groupInfo.FlightsDescription = flightsDescAll; return JsonView.Success(new AirTicketFeeInfoView() { CurrUserId = userId, GroupAirInfo = groupInfo, AirTicketFeeInfos = groupedByFlights }); } #region info 辅助方法 /// /// 将逗号分隔的字符串解析为 List(带错误处理) /// public static List ParseToIntListSafe(string input) { if (string.IsNullOrWhiteSpace(input)) return new List(); var result = new List(); var parts = input.Split(','); foreach (var part in parts) { if (int.TryParse(part.Trim(), out int value)) { result.Add(value); } // 可选:记录解析失败的值 } return result; } /// /// 提取序号辅助方法 /// private static int ExtractSequenceNo(string flightsDescription) { if (string.IsNullOrWhiteSpace(flightsDescription)) return int.MaxValue; var match = Regex.Match(flightsDescription, @"^(\d+)\."); return match.Success ? int.Parse(match.Groups[1].Value) : int.MaxValue; } #endregion /// /// 机票费用保存(关联 OriginalReservationId 与 RecordType 精准控制版) /// /// /// 【核心业务流向摘要】:
/// 1. 以前端数据为准:支持空客池与未设舱位(CType = 0)作为合法数据落地占位。
/// 2. 天网全局清扫:动态清理长变短(拆分)或短变长(合并)的航段冲突,不设时间线阻断。
/// 3. 退票信息拆分:通过 IsRefund 强核验 RefundRecord 存在性,按账户独立拆分为 RecordType = 1 的独立待保存对象。
/// 4. 循环内精准匹配与溯源:
/// - 正常票 (RecordType = 0):精准匹配老数据更新,若无则新增。
/// - 退票单 (RecordType = 1):除了精准匹配已有的退票单执行覆盖外;若是全新增的退票单,会动态溯源同航段同舱位下的“原正常购票单(RecordType = 0)”,将其 Id 注入 OriginalReservationId 字段。
/// 5. 两阶段安全隔离:循环内只管增改,循环结束后统一批量软删除脏数据并同步销毁信用付款单。
///
public async Task> SaveAsync(AirTicketFeeSaveDto dto) { var currUserId = dto.CurrUserId; var groupId = dto.GroupAirInfo?.Id ?? 0; var appPushBodyInfos = new List(); var skipMessages = new List(); // 1. 数据清洗:按照【航段+舱位+票据类型】分类并完成退票剥离 var saveList = await OrganizeAirTicketDataAsync(dto.AirTicketFeeInfos, groupId); if (saveList == null || !saveList.Any()) { return JsonView.Success("没有需要保存的数据", new AppPushBodyView()); } _sqlSugar.BeginTran(); try { // 2. 一次性批量预捞当前团组下所有有效客票并加更新锁 var dbAirList = await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => x.DIId == groupId && x.IsDel == 0) .ToListAsync(); var airIds = dbAirList.Select(x => x.Id).ToList(); var dbCardList = airIds.Any() ? await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => airIds.Contains(x.CId) && x.CTable == 85 && x.IsDel == 0) .ToListAsync() : new List(); var cabinTypeList = await _sqlSugar.Queryable().Where(x => x.IsDel == 0 && x.STid == 44).Select(x => new { x.Id, x.Name }).ToListAsync(); // ========================================================================= // 【全局清扫】 // ========================================================================= var allFrontendFlightText = string.Join("", saveList.Select(x => x.FlightsDescription)) .Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper(); var allDevouredDbAirs = dbAirList .Where(x => !string.IsNullOrEmpty(x.FlightsDescription)) .Where(x => { var cleanDbFlight = x.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper(); bool isExactMatchAnyForm = saveList.Any(f => f.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() == cleanDbFlight); if (isExactMatchAnyForm) return false; bool isShortContained = allFrontendFlightText.Contains(cleanDbFlight); bool isLongDevoured = saveList.Any(f => { var cleanFrontendItem = f.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper(); return !string.IsNullOrEmpty(cleanFrontendItem) && cleanDbFlight.Contains(cleanFrontendItem) && cleanDbFlight.Length > cleanFrontendItem.Length; }); return isShortContained || isLongDevoured; }).ToList(); var devouredDbAirIds = allDevouredDbAirs.Select(x => x.Id).ToHashSet(); var processedDbAirIds = new HashSet(); // 💡 建立新生成正常单的临时追踪字典,用于辅助同一批次中“同时新增正常单和退票单”时的内存级别 ID 溯源 var newCreatedNormalAirMap = new Dictionary(); // 3. 进入循环:精准等值比对 foreach (var item in saveList) { var cleanNewFlightDesc = item.FlightsDescription?.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() ?? ""; string normalLookupKey = $"{cleanNewFlightDesc}_{item.CType}"; // 用于绑定同一航段和舱位的 Key // 匹配库中完全同类型(正常对正常,退票对退票)的记录 var matchedDbAirs = dbAirList.Where(x => x.CType == item.CType && x.RecordType == item.RecordType && !processedDbAirIds.Contains(x.Id) && !devouredDbAirIds.Contains(x.Id) && !string.IsNullOrEmpty(x.FlightsDescription) ).Where(x => { var cleanDbFlightDesc = x.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper(); return cleanDbFlightDesc == cleanNewFlightDesc; }).ToList(); var dbAir = matchedDbAirs.FirstOrDefault(); decimal currentSegmentPrice = item.CustTicketInfos?.Sum(x => (x?.ActualPrice ?? 0M) + (x.AdditionalServices?.Where(y => y != null).Sum(y => y?.Amount ?? 0M) ?? 0M) ) ?? 0M; if (item.RecordType == 1) { currentSegmentPrice = item.CustTicketInfos?.Sum(x => (x?.RefundRecord?.RefundAmount ?? 0M) + (x?.RefundRecord?.NonRefundableTax ?? 0M) ) ?? 0M; } // -------------------- 分支 A:全新增 -------------------- if (dbAir == null) { var airEntity = _mapper.Map(item); airEntity.DIId = groupId; airEntity.CreateUserId = currUserId; airEntity.CreateTime = DateTime.Now; airEntity.IsDel = 0; airEntity.Price = currentSegmentPrice; airEntity.ClientNum = item.CustTicketInfos?.Count ?? 0; airEntity.ClientName = string.Join(",", item.CustTicketInfos?.Select(x => x.ClientId) ?? new List()); // :处理 RecordType 与 OriginalReservationId 关联 airEntity.RecordType = item.RecordType; // 显式落地前端洗出的类型 (0 或 1) if (item.RecordType == 1) // 如果当前是新增一条退票信息记录 { // 1. 优先从本次批量捞出的数据库中寻找对应的原购票正单 (RecordType == 0) var originalNormalDbRow = dbAirList.FirstOrDefault(x => x.RecordType == 0 && x.CType == item.CType && x.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() == cleanNewFlightDesc ); if (originalNormalDbRow != null) { airEntity.OriginalReservationId = originalNormalDbRow.Id; } // 2. 如果库里没有(说明原正常单也是本次一起新提交进来的),则从内存追踪字典中溯源获取 else if (newCreatedNormalAirMap.TryGetValue(normalLookupKey, out int memorizedNormalId)) { airEntity.OriginalReservationId = memorizedNormalId; } } else { airEntity.OriginalReservationId = 0; // 正常单默认为 0 } // 写入数据库客票预约表 var airId = await _sqlSugar.Insertable(airEntity).ExecuteReturnIdentityAsync(); // 如果当前新增的是正常购票单,将其登记到追踪字典,供后续可能存在的退票单进行指针勾连 if (airEntity.RecordType == 0) { if (!newCreatedNormalAirMap.ContainsKey(normalLookupKey)) { newCreatedNormalAirMap.Add(normalLookupKey, airId); } } // 伴生创建信用卡账单 var cardEntity = _mapper.Map(item); cardEntity.DIId = groupId; cardEntity.CId = airId; cardEntity.CTable = 85; cardEntity.IsDel = 0; cardEntity.CreateUserId = currUserId; cardEntity.CreateTime = DateTime.Now; cardEntity.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); cardEntity.PayMoney = currentSegmentPrice; cardEntity.PayThenMoney = cardEntity.PayMoney; decimal effectiveRate = cardEntity.DayRate > 0M ? cardEntity.DayRate : (item.DayRate > 0M ? item.DayRate : 1M); cardEntity.RMBPrice = cardEntity.PayMoney * effectiveRate; var cardId = await _sqlSugar.Insertable(cardEntity).ExecuteReturnIdentityAsync(); appPushBodyInfos.Add(new AppPushBodyInfo { OperationType = 1, AirTicketId = airId, CardId = cardId, CardCNYAmount = cardEntity.RMBPrice }); } // -------------------- 分支 B:编辑覆盖 -------------------- else { var dbCard = dbCardList.FirstOrDefault(x => x.CId == dbAir.Id); // 财务状态风控拦截 if (!CanModify(dbCard)) { var reason = dbCard?.IsPay == 1 ? "已付款" : dbCard?.IsAuditGM == 1 ? "已审核" : dbCard?.IsAuditGM == 3 ? "自动审核" : "禁止修改"; string cabinTypeName = "未设舱位"; if (item.CType > 0) cabinTypeName = cabinTypeList.FirstOrDefault(x => x.Id == item.CType)?.Name ?? $"舱位({item.CType})"; var shortFlight = item.FlightsDescription.Replace("\n", "").Replace("\r", "").Trim(); shortFlight = shortFlight.Length > 22 ? shortFlight.Substring(0, 22) + "..." : shortFlight; skipMessages.Add($"航段【{shortFlight}】{cabinTypeName}({(item.RecordType == 1 ? "退票" : "正常票")}-{reason})"); continue; } processedDbAirIds.Add(dbAir.Id); var frontendClients = item.CustTicketInfos ?? new List(); dbAir.FlightsDescription = item.FlightsDescription; dbAir.CType = item.CType; dbAir.CustTicketInfos = frontendClients; dbAir.ClientNum = frontendClients.Count; dbAir.ClientName = string.Join(",", frontendClients.Select(x => x.ClientId)); dbAir.Price = currentSegmentPrice; dbAir.PriceDescription = item.PriceDescription; // 💡 编辑时强制校正库里的 RecordType,防止数据发生类型偏转 dbAir.RecordType = item.RecordType; await _sqlSugar.Updateable(dbAir).ExecuteCommandAsync(); if (dbCard != null) { dbCard.PayMoney = dbAir.Price; dbCard.PayThenMoney = dbCard.PayMoney; dbCard.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); decimal effectiveRate = dbCard.DayRate > 0M ? dbCard.DayRate : (item.DayRate > 0M ? item.DayRate : 1M); dbCard.RMBPrice = dbAir.Price * effectiveRate; await _sqlSugar.Updateable(dbCard).ExecuteCommandAsync(); appPushBodyInfos.Add(new AppPushBodyInfo { OperationType = 2, AirTicketId = dbAir.Id, CardId = dbCard.Id, CardCNYAmount = dbCard.RMBPrice }); } } } // ========================================================================= // 【集中清扫】 // ========================================================================= if (allDevouredDbAirs.Any()) { var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var toDeleteIds = allDevouredDbAirs.Select(x => x.Id).ToList(); await _sqlSugar.Updateable() .SetColumns(x => new Grp_AirTicketReservations { IsDel = 1, DeleteUserId = currUserId, DeleteTime = nowStr }) .Where(x => toDeleteIds.Contains(x.Id)) .ExecuteCommandAsync(); await _sqlSugar.Updateable() .SetColumns(x => new Grp_CreditCardPayment { IsDel = 1, DeleteUserId = currUserId, DeleteTime = nowStr }) .Where(x => toDeleteIds.Contains(x.CId) && x.CTable == 85) .ExecuteCommandAsync(); } _sqlSugar.CommitTran(); var msg = "保存成功"; if (skipMessages.Any()) msg += Environment.NewLine + Environment.NewLine + "温馨提示:部分已审核的航段舱位未作变更:" + Environment.NewLine + string.Join(Environment.NewLine, skipMessages); return JsonView.Success(msg, new AppPushBodyView { GroupName = dto.GroupAirInfo?.TeamName, AppPushBodyInfos = appPushBodyInfos }); } catch (Exception ex) { _sqlSugar.RollbackTran(); return JsonView.Fail($"保存失败,系统崩溃回滚:{ex.Message}"); } } #region 辅助方法 /// /// 按航段+舱位+退票/正常票整理机票数据,完美放行“未选舱位(CType=0)”和“空客池占位” /// private async Task> OrganizeAirTicketDataAsync(List airInfos, int groupId) { var result = new List(); if (airInfos == null || !airInfos.Any()) return result; var dbCabinTypes = await _sqlSugar.Queryable() .Where(x => x.DIId == groupId && x.IsDel == 0) .Select(x => new { x.FlightsDescription, x.CType }) .ToListAsync(); foreach (var air in airInfos) { var currDbCabinTypes = dbCabinTypes .Where(x => x.FlightsDescription == air.FlightsDescription) .Select(x => x.CType) .Distinct() .ToList(); var submitCabinTypes = air.CustTicketInfos?.Select(x => x.CType).Distinct().ToList() ?? new List(); // 放行空客池占位特征 if (!submitCabinTypes.Any()) { submitCabinTypes.Add(0); } var cabinTypes = currDbCabinTypes.Union(submitCabinTypes).Distinct().ToList(); foreach (var cabinType in cabinTypes) { var cabinCustomers = air.CustTicketInfos?.Where(x => x.CType == cabinType).ToList() ?? new List(); // 1. 【正常票过滤】:非退票,或者虽然标记了 IsRefund 但 RefundRecord 类根本不存在的归入正常票 var normalCustomers = cabinCustomers; if (currDbCabinTypes.Contains(cabinType) || normalCustomers.Any() || (air.CustTicketInfos == null || !air.CustTicketInfos.Any())) { var normalPrice = normalCustomers.Sum(x => (x?.ActualPrice ?? 0M) + (x.AdditionalServices?.Where(y => y != null).Sum(y => y?.Amount ?? 0M) ?? 0M)); result.Add(new AirTicketFeeOpInfo { DIId = groupId, FlightsDescription = air.FlightsDescription, AirTicketBasicInfos = air.AirTicketBasicInfos, PriceDescription = air.PriceDescription, CType = cabinType, RecordType = 0, // 0:正常票 ClientNum = normalCustomers.Count, ClientName = string.Join(",", normalCustomers.Select(x => x.ClientId)), CustTicketInfos = normalCustomers, Price = normalPrice, Currency = air.PaymentCurrency, PayMoney = normalPrice, PaymentCurrency = air.PaymentCurrency, DayRate = air.DayRate > 0 ? air.DayRate : 1M }); } // 2. 【退票信息拆分】:严格验证 IsRefund 为 true 且 RefundRecord 类非空 var refundGroups = cabinCustomers .Where(x => x.IsRefund && x.RefundRecord != null) .GroupBy(x => x.RefundRecord.RefundAccount) .ToList(); foreach (var refundGroup in refundGroups) { var refundCustomers = refundGroup.ToList(); var refundAmount = refundCustomers.Sum(x => (x.RefundRecord?.RefundAmount ?? 0M) + (x.RefundRecord?.NonRefundableTax ?? 0M)); result.Add(new AirTicketFeeOpInfo { DIId = groupId, FlightsDescription = air.FlightsDescription, AirTicketBasicInfos = air.AirTicketBasicInfos, PriceDescription = air.PriceDescription, CType = cabinType, RecordType = 1, // 退票信息记录 CTDId = refundGroup.Key, ClientNum = refundCustomers.Count, ClientName = string.Join(",", refundCustomers.Select(x => x.ClientId)), CustTicketInfos = refundCustomers, Price = refundAmount, Currency = air.PaymentCurrency, PayMoney = refundAmount, PaymentCurrency = air.PaymentCurrency, DayRate = air.DayRate > 0M ? air.DayRate : 1M }); } } } return result; } /// /// 判断费用记录是否允许修改 /// private bool CanModify(Grp_CreditCardPayment card) { if (card == null) return true; if (card.IsPay == 1) return false; // 已付款禁止修改 if (card.IsAuditGM == 1) return false; // 已审核禁止修改 if (card.IsAuditGM == 3) return false; // 自动审核禁止修改 return true; } #endregion /// /// 行程内删除用户信息(2026版:支持多程/联程智能交集匹配,多用户并发安全) /// /// /// 【核心业务与技术逻辑】 /// 1. 联程降级追溯: 复用 FlightParser,若全字未匹配,按【航班号+起飞日期+航线】进行多程交集验证,防止单改多后人员删不掉。 /// 2. 并发隔离防脏读: 进入事务后立即注入 With(SqlWith.UpdLock) 行级锁,防止高并发下多人同时删除导致金额算错。 /// 3. 动态无损降级: 人员扣减后若仍有客,原地重算机票总价及卡片 RMBPrice;若全部走空,两表自动联动软删除(IsDel=1)。 /// public async Task TripUserDeleteAsync(AirTicketCostTripUserDelDto dto) { // 1. 批量基础数据预查(不纳入事务锁,减少锁持有时间) var cabinDatas = await _sqlSugar.Queryable() .Where(x => x.IsDel == 0 && x.STid == 44) .Select(x => new { x.Id, x.Name }) .ToListAsync(); var cabinName = cabinDatas.FirstOrDefault(x => x.Id == dto.CType)?.Name ?? "未知舱位"; var clientInfo = await _sqlSugar.Queryable() .Where(x => x.IsDel == 0 && x.Id == dto.ClientId) .Select(x => new { x.Id, x.FirstName, x.LastName }) .FirstAsync(); var clientName = clientInfo != null ? $"{AesEncryptionHelper.Decrypt(clientInfo.FirstName)}{AesEncryptionHelper.Decrypt(clientInfo.LastName)}" : $"ID:{dto.ClientId}"; // 2. 开启事务与高并发行级锁控制 _sqlSugar.BeginTran(); try { // 2.1 捞取当前团组、当前舱位下的所有潜在候选机票记录,并加更新锁 var dbAirList = await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => x.DIId == dto.GroupId && x.CType == dto.CType && x.RecordType == 0 && x.IsDel == 0) .ToListAsync(); // 复用保存逻辑的“黑屏格式原子航段强类型验证” // A. 优先全字匹配 var airTicket = dbAirList.FirstOrDefault(x => x.FlightsDescription == dto.FlightsDescription); // B. 降级匹配:支持单程改多程后的精准追溯 if (airTicket == null) { var currentTargetFlights = FlightParser.ParseFlights(dto.FlightsDescription); if (currentTargetFlights != null && currentTargetFlights.Any()) { var targetKeys = currentTargetFlights .Where(f => !string.IsNullOrEmpty(f.FlightNumber)) .Select(f => $"{f.FlightNumber.Trim().ToUpper()}-{f.DepartureDate:yyyyMMdd}-{f.DepartureAirport.Trim().ToUpper()}{f.ArrivalAirport.Trim().ToUpper()}") .ToHashSet(); airTicket = dbAirList.FirstOrDefault(x => (FlightParser.ParseFlights(x.FlightsDescription) ?? new List()) .Any(oldFlight => { if (string.IsNullOrEmpty(oldFlight.FlightNumber)) return false; var oldKey = $"{oldFlight.FlightNumber.Trim().ToUpper()}-{oldFlight.DepartureDate:yyyyMMdd}-{oldFlight.DepartureAirport.Trim().ToUpper()}{oldFlight.ArrivalAirport.Trim().ToUpper()}"; return targetKeys.Contains(oldKey); })); } } if (airTicket == null) { _sqlSugar.RollbackTran(); return JsonView.Fail($"未找到对应记录!(航段:{dto.FlightsDescription}, 舱位:{cabinName})"); } // 3. 检查人员存在性与退票安全校验 var allCustTicketInfos = airTicket.CustTicketInfos ?? new List(); var targetClient = allCustTicketInfos.FirstOrDefault(ct => ct.ClientId == dto.ClientId); if (targetClient == null) { _sqlSugar.RollbackTran(); return JsonView.Fail($"人员【{clientName}】不存在于该机票记录中!"); } if (targetClient.IsRefund) { _sqlSugar.RollbackTran(); return JsonView.Fail($"删除失败:人员【{clientName}】在该航段存在退票记录,不可删除!请先走退票财务账目。"); } // 4. 查询关联的信用卡付款记录并验证财务状态 var cardPayment = await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => x.DIId == dto.GroupId && x.CId == airTicket.Id && x.CTable == 85 && x.IsDel == 0) .FirstAsync(); if (cardPayment != null) { var (canModify, reason) = cardPayment.IsPay == 1 ? (false, "已付款") : cardPayment.IsAuditGM == 1 ? (false, "已审核") : cardPayment.IsAuditGM == 3 ? (false, "自动审核") : (true, string.Empty); if (!canModify) { _sqlSugar.RollbackTran(); return JsonView.Fail($"删除失败:当前航段对应的费用单【{reason}】,锁定不可更改!(客户:{clientName})"); } } // 5. 执行排除扣减 var remainingClients = allCustTicketInfos.Where(ct => ct.ClientId != dto.ClientId).ToList(); var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); if (remainingClients.Any()) { // ------------------------------------ // 策略 A:仍有剩余乘客 ➔ 原地重算金额并更新 // ------------------------------------ decimal totalPrice = remainingClients.Sum(x => (x.ActualPrice) + (x.AdditionalServices?.Sum(y => y.Amount) ?? 0)); airTicket.Price = totalPrice; airTicket.ClientNum = remainingClients.Count; airTicket.ClientName = string.Join(",", remainingClients.Select(x => x.ClientId)); airTicket.CustTicketInfos = remainingClients; await _sqlSugar.Updateable(airTicket) .UpdateColumns(x => new { x.ClientNum, x.ClientName, x.Price, x.CustTicketInfos }) .ExecuteCommandAsync(); if (cardPayment != null) { cardPayment.PayMoney = totalPrice; cardPayment.PayThenMoney = totalPrice; cardPayment.RMBPrice = totalPrice * (cardPayment.DayRate > 0 ? cardPayment.DayRate : 1); cardPayment.UpdateDate = nowStr; await _sqlSugar.Updateable(cardPayment) .UpdateColumns(x => new { x.PayMoney, x.PayThenMoney, x.RMBPrice, x.UpdateDate }) .ExecuteCommandAsync(); } } else { // ------------------------------------ // 策略 B:人走楼空(无剩余乘客)➔ 柔性软删除整单 // ------------------------------------ airTicket.IsDel = 1; airTicket.DeleteUserId = dto.CurrUserId; airTicket.DeleteTime = nowStr; await _sqlSugar.Updateable(airTicket) .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime }) .ExecuteCommandAsync(); if (cardPayment != null) { cardPayment.IsDel = 1; cardPayment.DeleteUserId = dto.CurrUserId; cardPayment.DeleteTime = nowStr; await _sqlSugar.Updateable(cardPayment) .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime }) .ExecuteCommandAsync(); } } _sqlSugar.CommitTran(); // 航段简述 var threeCodeFlightDesc = BuildThreeCodeFlightDesc(airTicket.FlightsDescription); return JsonView.Success($"删除成功!已从航段【{threeCodeFlightDesc}】中成功移除人员【{clientName}】"); } catch (Exception ex) { _sqlSugar.RollbackTran(); return JsonView.Fail($"删除失败:{ex.Message}"); } } /// /// 取消指定客户的退票(2026版:支持多程/联程智能交集匹配,多用户并发安全) /// /// /// 核心业务与技术逻辑】 /// 1. 联程黑屏格式验证: 统一采用 FlightParser。若由于航段在后续操作中被组装或裁剪变长,可通过【航班号+起飞日期+航线】跨越文本排版差异精准定位退票单。 /// 2. 严格事务级 UpdLock 锁: 进入事务后立即向潜在修改的退票池与正常客票池注入 With(SqlWith.UpdLock),强力避免多人并发取消或退改造成的金额和状态覆盖死锁。 /// 3. 动态退改解耦: 重算剩余退票客人的退款总额(包含附加退税)。若人走楼空,系统自动执行联动两表软删除(IsDel=1)。 /// public async Task CancelClientRefundAsync(CancelClientRefundDto dto) { // 1. 基础人员信息查询(明文解密,非敏感阶段不抢占数据库连接锁) var clientInfo = await _sqlSugar.Queryable() .Where(x => x.IsDel == 0 && x.Id == dto.ClientId) .Select(x => new { x.Id, x.FirstName, x.LastName }) .FirstAsync(); var clientName = clientInfo != null ? $"{AesEncryptionHelper.Decrypt(clientInfo.FirstName)}{AesEncryptionHelper.Decrypt(clientInfo.LastName)}" : $"ID:{dto.ClientId}"; // 2. 开启事务与行锁防护 _sqlSugar.BeginTran(); try { // 2.1 批量加载当前团组、当前舱位下的所有退票主记录(RecordType == 1)并上锁 var dbRefundAirList = await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => x.DIId == dto.GroupId && x.CType == dto.CType && x.RecordType == 1 && x.IsDel == 0) .ToListAsync(); // GDS黑屏格式多程交集寻找退票单 var refundRecord = dbRefundAirList.FirstOrDefault(x => x.FlightsDescription == dto.FlightsDescription); if (refundRecord == null) { var targetFlights = FlightParser.ParseFlights(dto.FlightsDescription); if (targetFlights != null && targetFlights.Any()) { var targetKeys = targetFlights .Where(f => !string.IsNullOrEmpty(f.FlightNumber)) .Select(f => $"{f.FlightNumber.Trim().ToUpper()}-{f.DepartureDate:yyyyMMdd}-{f.DepartureAirport.Trim().ToUpper()}{f.ArrivalAirport.Trim().ToUpper()}") .ToHashSet(); refundRecord = dbRefundAirList.FirstOrDefault(x => (FlightParser.ParseFlights(x.FlightsDescription) ?? new List()) .Any(oldFlight => { if (string.IsNullOrEmpty(oldFlight.FlightNumber)) return false; var oldKey = $"{oldFlight.FlightNumber.Trim().ToUpper()}-{oldFlight.DepartureDate:yyyyMMdd}-{oldFlight.DepartureAirport.Trim().ToUpper()}{oldFlight.ArrivalAirport.Trim().ToUpper()}"; return targetKeys.Contains(oldKey); })); } } if (refundRecord == null) { _sqlSugar.RollbackTran(); return JsonView.Fail("未找到对应的退票记录!"); } // 3. 校验退票财务账目状态 var refundPayment = await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => x.DIId == dto.GroupId && x.CId == refundRecord.Id && x.CTable == 85 && x.IsDel == 0) .FirstAsync(); if (refundPayment != null) { var (canCancel, reason) = refundPayment.IsPay == 1 ? (false, "退票费用已付款") : refundPayment.IsAuditGM == 1 ? (false, "退票费用已审核") : refundPayment.IsAuditGM == 3 ? (false, "退票费用已自动审核") : (true, string.Empty); if (!canCancel) { _sqlSugar.RollbackTran(); return JsonView.Fail($"取消失败:{reason},财务单据已锁定不可逆向调整!"); } } // 4. 解析退票人员列表存在性 var allRefundClients = refundRecord.CustTicketInfos ?? new List(); if (!allRefundClients.Any(x => x.ClientId == dto.ClientId && x.IsRefund)) { _sqlSugar.RollbackTran(); return JsonView.Fail($"人员【{clientName}】不存在于该机票的有效退票名单中!"); } // 5. 捞取并锁定关联的“正常”机票主单(RecordType == 0) // 优先通过 OriginalReservationId 溯源,若老数据为空则通过黑屏航段链兜底 var normalTicket = await _sqlSugar.Queryable() .With(SqlWith.UpdLock) .Where(x => x.DIId == dto.GroupId && x.CType == dto.CType && x.RecordType == 0 && x.IsDel == 0) .WhereIF(refundRecord.OriginalReservationId > 0, x => x.Id == refundRecord.OriginalReservationId) .FirstAsync(); if (normalTicket == null) { _sqlSugar.RollbackTran(); return JsonView.Fail("取消失败:该退票单关联的正常客票主记录已不存在或被物理隔离!"); } // 6. 状态反转:恢复正常票中该客户的健康客票状态 var normalTicketClients = normalTicket.CustTicketInfos ?? new List(); var targetNormalClient = normalTicketClients.FirstOrDefault(x => x.ClientId == dto.ClientId); if (targetNormalClient != null) { targetNormalClient.IsRefund = false; targetNormalClient.RefundRecord = null; } else { // 极端防御:如果正常票里之前漏掉了这个人,执行自我修复式追加 var refundClientData = allRefundClients.First(x => x.ClientId == dto.ClientId); refundClientData.IsRefund = false; refundClientData.RefundRecord = null; normalTicketClients.Add(refundClientData); } normalTicket.CustTicketInfos = normalTicketClients; await _sqlSugar.Updateable(normalTicket) .UpdateColumns(x => new { x.CustTicketInfos }) .ExecuteCommandAsync(); // 7. 从退票池中剔除此人并重算 var remainingRefundClients = allRefundClients.Where(x => x.ClientId != dto.ClientId).ToList(); var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); if (remainingRefundClients.Any()) { // ------------------------------------ // 策略 A:还有其余乘客在退票 ➔ 原地重算并更新退减金额 // ------------------------------------ decimal totalRefundAmount = remainingRefundClients.Sum(x => (x.RefundRecord?.RefundAmount ?? 0M) + (x.RefundRecord?.NonRefundableTax ?? 0M)); refundRecord.CustTicketInfos = remainingRefundClients; refundRecord.ClientNum = remainingRefundClients.Count; refundRecord.ClientName = string.Join(",", remainingRefundClients.Select(x => x.ClientId)); refundRecord.Price = totalRefundAmount; await _sqlSugar.Updateable(refundRecord) .UpdateColumns(x => new { x.CustTicketInfos, x.ClientNum, x.ClientName, x.Price }) .ExecuteCommandAsync(); if (refundPayment != null) { refundPayment.PayMoney = totalRefundAmount; refundPayment.PayThenMoney = totalRefundAmount; refundPayment.RMBPrice = totalRefundAmount * (refundPayment.DayRate > 0M ? refundPayment.DayRate : 1M); refundPayment.UpdateDate = nowStr; await _sqlSugar.Updateable(refundPayment) .UpdateColumns(x => new { x.PayMoney, x.PayThenMoney, x.RMBPrice, x.UpdateDate }) .ExecuteCommandAsync(); } } else { // ------------------------------------ // 策略 B:退票库已完全撤回清空 ➔ 软删除整条退票单及财务记录 // ------------------------------------ refundRecord.IsDel = 1; refundRecord.DeleteUserId = dto.CurrUserId; refundRecord.DeleteTime = nowStr; await _sqlSugar.Updateable(refundRecord) .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime }) .ExecuteCommandAsync(); if (refundPayment != null) { refundPayment.IsDel = 1; refundPayment.DeleteUserId = dto.CurrUserId; refundPayment.DeleteTime = nowStr; await _sqlSugar.Updateable(refundPayment) .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime }) .ExecuteCommandAsync(); } } _sqlSugar.CommitTran(); // 航段简述 var threeCodeFlightDesc = BuildThreeCodeFlightDesc(refundRecord.FlightsDescription); return JsonView.Success($"取消退票成功!已将客户【{clientName}】从航段【{threeCodeFlightDesc}】的退票池中移除,客票状态已恢复正常。"); } catch (Exception ex) { _sqlSugar.RollbackTran(); return JsonView.Fail($"取消退票失败:{ex.Message}"); } } /// /// 将黑屏标准航段文本转换为极简的【航班号+三字码行程链+起飞日期】中文提示 /// 示例:LH098(FRA ➔ MUC 2026-05-28);LH2440(MUC ➔ CPH 2026-05-29) /// private string BuildThreeCodeFlightDesc(string flightsDescription) { var flightInfoList = FlightParser.ParseFlights(flightsDescription); if (flightInfoList != null && flightInfoList.Any()) { return string.Join(";", flightInfoList.Select(f => { var flightNo = f.FlightNumber?.Trim().ToUpper() ?? "未知航班"; var dept = f.DepartureAirport?.Trim().ToUpper() ?? "???"; var arr = f.ArrivalAirport?.Trim().ToUpper() ?? "???"; return $"{flightNo}({dept} ➔ {arr} {f.DepartureDate:yyyy-MM-dd})"; })); } return flightsDescription; // 兜底返回原串 } /// /// 按ID删除机票记录 2026版 /// 验证费用状态和退票记录 /// /// 操作用户ID /// 机票记录ID /// public async Task DeleteByIdAsync(int userId, int airTicketId) { BeginTran(); try { // 1. 查询机票记录 var airTicket = await _sqlSugar.Queryable() .Where(x => x.Id == airTicketId && x.IsDel == 0) .FirstAsync(); if (airTicket == null) { RollbackTran(); return JsonView.Fail("机票费用记录不存在或已被删除!"); } // 2. 验证费用状态 var cardPayment = await _sqlSugar.Queryable() .Where(x => x.CId == airTicketId && x.CTable == 85 && x.IsDel == 0) .FirstAsync(); if (cardPayment != null) { if (cardPayment.IsAuditGM == 1) { RollbackTran(); return JsonView.Fail($"删除失败:当前费用已审核,不可删除!(记录ID:{airTicketId})"); } if (cardPayment.IsAuditGM == 3) { RollbackTran(); return JsonView.Fail($"删除失败:当前费用已自动审核,不可删除!(记录ID:{airTicketId})"); } if (cardPayment.IsPay == 1) { RollbackTran(); return JsonView.Fail($"删除失败:当前费用已付款,不可删除!(记录ID:{airTicketId})"); } } // 3. 验证退票记录关联 if (airTicket.RecordType == 0) // 正常记录 { // 检查是否有退票记录关联 var refundRecords = await _sqlSugar.Queryable() .Where(x => x.OriginalReservationId == airTicketId && x.RecordType == 1 && x.IsDel == 0) .ToListAsync(); if (refundRecords.Any()) { var refundIds = string.Join(",", refundRecords.Select(x => x.Id)); var refundNames = string.Join(",", refundRecords.Select(x => x.ClientName)); RollbackTran(); return JsonView.Fail($"删除失败:该记录存在关联的退票记录!请先删除以下退票记录ID:[{refundIds}],关联人员:[{refundNames}]"); } } else if (airTicket.RecordType == 1) // 退票记录 { // 检查关联的正常机票是否存在 if (airTicket.OriginalReservationId > 0) { var normalTicket = await _sqlSugar.Queryable() .Where(x => x.Id == airTicket.OriginalReservationId && x.IsDel == 0) .FirstAsync(); if (normalTicket == null) { RollbackTran(); return JsonView.Fail($"删除失败:退票记录关联的正常机票不存在或已被删除!(关联ID:{airTicket.OriginalReservationId})"); } } } // 4. 执行软删除 // 4.1 软删除机票记录 airTicket.IsDel = 1; airTicket.DeleteUserId = userId; airTicket.DeleteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var airUpdateCount = await _sqlSugar.Updateable(airTicket) .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime }) .Where(x => x.Id == airTicketId) .ExecuteCommandAsync(); if (airUpdateCount < 1) { RollbackTran(); return JsonView.Fail("删除失败:机票记录删除失败!"); } // 4.2 软删除对应的费用记录 if (cardPayment != null) { cardPayment.IsDel = 1; cardPayment.DeleteUserId = userId; cardPayment.DeleteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var cardUpdateCount = await _sqlSugar.Updateable(cardPayment) .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime }) .Where(x => x.Id == cardPayment.Id) .ExecuteCommandAsync(); if (cardUpdateCount < 1) { RollbackTran(); return JsonView.Fail("删除失败:费用记录删除失败!"); } } CommitTran(); var recordTypeName = airTicket.RecordType == 0 ? "正常记录" : "退票记录"; return JsonView.Success($"删除成功!已删除{recordTypeName},ID:{airTicketId}"); } catch (Exception ex) { RollbackTran(); return JsonView.Fail($"删除失败:{ex.Message}"); } } /// /// 机票费用报表数据源(全量解析不含时间隔离版) /// /// /// public async Task> DeriveAsync(AirTicketCostDeriveDto dto) { // 1. 捞取机票基础预约与关联账单数据 string sql = string.Format(@"Select atr.*,ccp.Payee,ccp.BankNo, sd4.Name as PayType, ccp.IsAuditGM, sd1.Name As 'CTypeName', sd2.Name As 'PreCurrencyStr', sd3.Name As 'CurrencyStr' From Grp_AirTicketReservations atr Left Join Grp_CreditCardPayment ccp On atr.Id = ccp.CId And atr.DIId = ccp.DIId And ccp.IsDel = 0 Left Join Sys_SetData sd1 On sd1.Id = atr.ctype Left Join Sys_SetData sd2 On sd2.Id = atr.PreCurrency Left Join Sys_SetData sd3 On sd3.Id = atr.Currency left join Sys_SetData sd4 On sd4.Id = ccp.PayDId Where atr.IsDel = 0 And atr.DIId={0} Order By atr.FlightsDate Asc", dto.DiId); var airInfoList = await _sqlSugar.SqlQueryable(sql).ToListAsync(); if (airInfoList.Any()) { // 2. 预捞服务类型基础数据 var serviceTypeList = await _sqlSugar.Queryable() .Where(x => x.IsDel == 0 && x.STid == 139) .Select(x => new { x.Id, x.Name }) .ToListAsync(); // 3. 预捞客户信息并全量执行内存解密 var clientDatas = await _sqlSugar.Queryable() .LeftJoin((x, y) => x.ClientId == y.Id) .Where((x, y) => x.IsDel == 0 && x.DiId == dto.DiId) .Select((x, y) => new { y.Id, y.FirstName, y.LastName }) .ToListAsync(); var clientNameList = clientDatas.Select(x => { return new { x.Id, FirstName = AesEncryptionHelper.Decrypt(x.FirstName), LastName = AesEncryptionHelper.Decrypt(x.LastName), }; }).ToList(); foreach (var item in airInfoList) { #region 银行账号脱敏敏感过滤 if (item.BankType == "其他") { item.BankNo = "--"; } else if (!string.IsNullOrEmpty(item.BankType)) { // 防呆截取保护:确保长度足够才执行旧版的前3位切片拼接 var bankNoStr = item.BankNo ?? ""; var safeBankNo = bankNoStr.Length >= 3 ? bankNoStr[..3] : bankNoStr; item.BankNo = $"{item.BankType}:{safeBankNo}"; } #endregion var clientIds = item.ClientNum > 0 ? (item.ClientName ?? "").Split(',').Select(idStr => int.TryParse(idStr, out var id) ? id : (int?)null).Where(id => id.HasValue).Select(id => id.Value).ToList() : new List(); var clientNames = clientNameList.Where(c => clientIds.Contains(c.Id)).Select(c => $"{c.FirstName}{c.LastName}").ToList(); item.ClientName = string.Join("、", clientNames); // 新数据单独处理 if (item.CreateTime < GlobalConfig.AirTicketIntegrationDateTime) continue; // 航段文本深度解析 var flightInfoList = FlightParser.ParseFlights(item.FlightsDescription); if (!flightInfoList.Any()) { continue; // 如果老数据完全不符合航段格式,容错跳过字符拼接 } // 拼接航段城市串 (如:PEK ➔ DXB / DXB ➔ LHR) item.FlightsCity = string.Join(@" / ", flightInfoList.Select(f => { var dept = f.DepartureAirport?.Trim().ToUpper() ?? "???"; var arr = f.ArrivalAirport?.Trim().ToUpper() ?? "???"; return $"{dept}-{arr}"; })); // 提取首段航班号作为主航班号展现 item.FlightsCode = flightInfoList.FirstOrDefault()?.FlightNumber ?? "未知航班"; // 附加服务附加名称处理 var serviceTypes = item.CustTicketInfos? .SelectMany(cti => cti?.AdditionalServices ?? new List()) .Where(s => s != null) .Select(s => serviceTypeList.FirstOrDefault(st => st.Id == s.ServiceTypeId)?.Name ?? "未知服务") .Distinct() .ToList() ?? new List(); // 💡 处理 RecordType 值:若是退票单,显式追加“退票”资产状态标签 if (item.RecordType == 1) { item.CTypeName += " - 退票"; } if (serviceTypes.Any()) { item.CTypeName += $"(含{string.Join("、", serviceTypes)}等服务)"; } // 金额单价防呆计算 if (item.RecordType == 0) { // 防止历史脏数据中乘机人数为 0 导致除以零系统崩溃 item.PrePrice = item.ClientNum > 0 ? (item.Price / item.ClientNum) : item.Price; } } // 4. 捞取团组上下文与操作员名称 var groupInfo = _sqlSugar.Queryable().Where(a => a.Id == dto.DiId && a.IsDel == 0).Select(a => new { a.Id, a.TeamName, a.TourCode }).First(); var userName = _sqlSugar.Queryable().Where(a => a.Id == dto.UserId && a.IsDel == 0).Select(a => a.CnName).First() ?? "-"; return JsonView.Success("操作成功", new DeriveView() { airInfoList = airInfoList, TeamName = groupInfo?.TeamName ?? "未知团组", TourCode = groupInfo?.TourCode ?? "-", Opeator = userName }); } return JsonView.Fail("获取失败,未查询到相关机票报表数据"); } public class DeriveView { public List airInfoList { get; set; } public string TeamName { get; set; } public string TourCode { get; set; } public string Opeator { get; set; } } public async Task TripListAsync(ItineraryAirTicketResDto dto) { string sql = string.Format(@"select a.*,c.IsAuditGM,(select Name from Sys_SetData where Id=a.cType) as 'CTypeName',(select Name from Sys_SetData where Id=a.PreCurrency) as 'PreCurrencyStr',(select Name from Sys_SetData where Id=a.Currency) as 'CurrencyStr' from Grp_AirTicketReservations a,Grp_CreditCardPayment c where a.id=c.CId and a.isdel={1} and a.DIId={0} Order By a.CreateTime desc", dto.DiId, 0); List _AirTicketReservations = _sqlSugar.SqlQueryable(sql).ToList(); if (_AirTicketReservations.Count == 0) { return new Result() { Code = -1, Msg = "暂无数据", Data = null }; } else { return new Result() { Code = 0, Msg = "查询成功", Data = _AirTicketReservations }; } } #endregion }