Bläddra i källkod

机票费用导出与业务逻辑优化,支持多语言行程单

本次提交主要优化了机票费用相关的业务逻辑与导出功能,包括:报表导出支持单元格合并,行程单导出重构为中英文模板,航段/舱位/类型数据去重,退票金额统一为负数并自动审核,批量预加载客户与服务类型数据,DTO结构补充,SQL与数据处理流程优化,提升了数据准确性、性能和代码可维护性。
Lyyyi 11 timmar sedan
förälder
incheckning
24c67c8741

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 500 - 510
OASystem/OASystem.Api/Controllers/GroupsController.cs


+ 3 - 0
OASystem/OASystem.Domain/ViewModels/Groups/AirTicketReservationsView.cs

@@ -8,6 +8,7 @@ public class AirTicketReservationsView : Grp_AirTicketReservations
     /// 总经理是否审核
     /// 总经理是否审核
     /// </summary>
     /// </summary>
     public int IsAuditGM { get; set; }
     public int IsAuditGM { get; set; }
+    public string Payee { get; set; }
     /// <summary>
     /// <summary>
     /// 舱类型
     /// 舱类型
     /// </summary>
     /// </summary>
@@ -18,6 +19,8 @@ public class AirTicketReservationsView : Grp_AirTicketReservations
     public string FlightDescription { get; set; }
     public string FlightDescription { get; set; }
     public string ClientNameStr { get; set; }
     public string ClientNameStr { get; set; }
     public int IsPay { get; set; }
     public int IsPay { get; set; }
+
+    public List<FlightInfo> FlightInfoList { get; set; }
 }
 }
 
 
 
 

+ 17 - 0
OASystem/OASystem.Domain/ViewModels/JsonView.cs

@@ -82,6 +82,23 @@ public class JsonView
         };
         };
     }
     }
 
 
+    /// <summary>
+    /// 失败
+    /// </summary>
+    /// <param name="code"></param>
+    /// <param name="msg"></param>
+    /// <returns></returns>
+    public static JsonView Fail(int code,string msg)
+    {
+        return new JsonView()
+        {
+            Code = code,
+            Count = 0,
+            Data = null,
+            Msg = msg,
+        };
+    }
+
     /// <summary>
     /// <summary>
     /// 失败
     /// 失败
     /// </summary>
     /// </summary>

+ 261 - 115
OASystem/OASystem.Infrastructure/Repositories/Groups/AirTicketResRepository.cs

@@ -1468,14 +1468,14 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 var allCustTicketInfos = sortedGroup.SelectMany(x => x.Air.CustTicketInfos ?? new List<CustTicketInfo>()).ToList();
                 var allCustTicketInfos = sortedGroup.SelectMany(x => x.Air.CustTicketInfos ?? new List<CustTicketInfo>()).ToList();
 
 
                 // 汇总金额
                 // 汇总金额
-                decimal totalPayMoney = primaryItem?.Air?.CustTicketInfos?.Sum(c =>
+                decimal totalPayMoney = allCustTicketInfos?.Sum(c =>
                 {
                 {
                     // 机票票面价 + 附加服务费
                     // 机票票面价 + 附加服务费
                     decimal ticketAndServices = (c?.ActualPrice ?? 0M) + (c?.AdditionalServices?.Sum(d => d?.Amount ?? 0M) ?? 0M);
                     decimal ticketAndServices = (c?.ActualPrice ?? 0M) + (c?.AdditionalServices?.Sum(d => d?.Amount ?? 0M) ?? 0M);
                     // 退票相关费用(退票金额通常为负值,退款给客户)
                     // 退票相关费用(退票金额通常为负值,退款给客户)
                     decimal refundFees = (c?.RefundRecord?.RefundAmount ?? 0M) + (c?.RefundRecord?.NonRefundableTax ?? 0M);
                     decimal refundFees = (c?.RefundRecord?.RefundAmount ?? 0M) + (c?.RefundRecord?.NonRefundableTax ?? 0M);
 
 
-                    return ticketAndServices + refundFees;
+                    return ticketAndServices - refundFees;
                 }) ?? 0M;
                 }) ?? 0M;
 
 
                 // 多舱位状态精炼串联
                 // 多舱位状态精炼串联
@@ -1521,6 +1521,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     CardholderName = primaryItem.Card.CardholderName,
                     CardholderName = primaryItem.Card.CardholderName,
                     DayRate = primaryItem.Card.DayRate,
                     DayRate = primaryItem.Card.DayRate,
                     CompanyBankNo = primaryItem.Card.CompanyBankNo,
                     CompanyBankNo = primaryItem.Card.CompanyBankNo,
+                    OtherBankName = primaryItem.Card.OtherBankName,
                     OtherSideNo = primaryItem.Card.OtherSideNo,
                     OtherSideNo = primaryItem.Card.OtherSideNo,
                     OtherSideName = primaryItem.Card.OtherSideName,
                     OtherSideName = primaryItem.Card.OtherSideName,
                     Payee = primaryItem.Card.Payee,
                     Payee = primaryItem.Card.Payee,
@@ -1666,28 +1667,48 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
             var devouredDbAirIds = allDevouredDbAirs.Select(x => x.Id).ToHashSet();
             var devouredDbAirIds = allDevouredDbAirs.Select(x => x.Id).ToHashSet();
             var processedDbAirIds = new HashSet<int>();
             var processedDbAirIds = new HashSet<int>();
 
 
-            // 💡 建立新生成正常单的临时追踪字典,用于辅助同一批次中“同时新增正常单和退票单”时的内存级别 ID 溯源
+            // 建立数据库记录的精确索引,每个组合只保留一条记录
+            var dbAirIndex = dbAirList
+                .Where(x => !devouredDbAirIds.Contains(x.Id))
+                .GroupBy(x => new
+                {
+                    CleanFlightsDesc = x.FlightsDescription?.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() ?? "",
+                    x.CType,
+                    x.RecordType
+                })
+                .ToDictionary(
+                    g => g.Key,
+                    g => g.OrderBy(x => x.Id).FirstOrDefault()
+                );
+
+            // 建立新生成正常单的临时追踪字典
             var newCreatedNormalAirMap = new Dictionary<string, int>();
             var newCreatedNormalAirMap = new Dictionary<string, int>();
 
 
+            // 对 saveList 也进行去重,确保每个组合只有一条
+            var distinctSaveList = saveList
+                .GroupBy(x => new
+                {
+                    CleanFlightsDesc = x.FlightsDescription?.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() ?? "",
+                    x.CType,
+                    x.RecordType
+                })
+                .Select(g => g.First())
+                .ToList();
+
             // 3. 进入循环:精准等值比对
             // 3. 进入循环:精准等值比对
-            foreach (var item in saveList)
+            foreach (var item in distinctSaveList)
             {
             {
                 var cleanNewFlightDesc = item.FlightsDescription?.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() ?? "";
                 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();
+                string normalLookupKey = $"{cleanNewFlightDesc}_{item.CType}";
+
+                var lookupKey = new
+                {
+                    CleanFlightsDesc = cleanNewFlightDesc,
+                    item.CType,
+                    item.RecordType
+                };
 
 
-                var dbAir = matchedDbAirs.FirstOrDefault();
+                dbAirIndex.TryGetValue(lookupKey, out var dbAir);
 
 
                 decimal currentSegmentPrice = item.CustTicketInfos?.Sum(x =>
                 decimal currentSegmentPrice = item.CustTicketInfos?.Sum(x =>
                             (x?.ActualPrice ?? 0M) + (x.AdditionalServices?.Where(y => y != null).Sum(y => y?.Amount ?? 0M) ?? 0M)
                             (x?.ActualPrice ?? 0M) + (x.AdditionalServices?.Where(y => y != null).Sum(y => y?.Amount ?? 0M) ?? 0M)
@@ -1700,24 +1721,33 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                         ) ?? 0M;
                         ) ?? 0M;
                 }
                 }
 
 
+                // 更新或新增前,先从索引中移除,防止重复使用
+                if (dbAir != null)
+                {
+                    dbAirIndex.Remove(lookupKey);
+                }
+
                 // -------------------- 分支 A:全新增 --------------------
                 // -------------------- 分支 A:全新增 --------------------
                 if (dbAir == null)
                 if (dbAir == null)
                 {
                 {
                     var airEntity = _mapper.Map<Grp_AirTicketReservations>(item);
                     var airEntity = _mapper.Map<Grp_AirTicketReservations>(item);
-                    airEntity.DIId = groupId; airEntity.CreateUserId = currUserId; airEntity.CreateTime = DateTime.Now; airEntity.IsDel = 0;
+                    airEntity.DIId = groupId;
+                    airEntity.CreateUserId = currUserId;
+                    airEntity.CreateTime = DateTime.Now;
+                    airEntity.IsDel = 0;
                     airEntity.Price = currentSegmentPrice;
                     airEntity.Price = currentSegmentPrice;
-                    airEntity.ClientNum = item.CustTicketInfos?.Count ?? 0;
-                    airEntity.ClientName = string.Join(",", item.CustTicketInfos?.Select(x => x.ClientId) ?? new List<int>());
+                    airEntity.ClientNum = item.CustTicketInfos?.Select(x => x.ClientId).Distinct().Count() ?? 0;
+                    airEntity.ClientName = string.Join(",", item.CustTicketInfos?.Select(x => x.ClientId).Distinct() ?? new List<int>());
 
 
-                    // :处理 RecordType 与 OriginalReservationId 关联
-                    airEntity.RecordType = item.RecordType; // 显式落地前端洗出的类型 (0 或 1)
+                    airEntity.RecordType = item.RecordType;
 
 
-                    if (item.RecordType == 1) // 如果当前是新增一条退票信息记录
+                    if (item.RecordType == 1)
                     {
                     {
-                        // 1. 优先从本次批量捞出的数据库中寻找对应的原购票正单 (RecordType == 0)
+                        // 优先从已存在但不被删除的记录中查找原购票正单
                         var originalNormalDbRow = dbAirList.FirstOrDefault(x =>
                         var originalNormalDbRow = dbAirList.FirstOrDefault(x =>
                             x.RecordType == 0 &&
                             x.RecordType == 0 &&
                             x.CType == item.CType &&
                             x.CType == item.CType &&
+                            !devouredDbAirIds.Contains(x.Id) &&
                             x.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() == cleanNewFlightDesc
                             x.FlightsDescription.Replace(" ", "").Replace("\n", "").Replace("\r", "").Trim().ToUpper() == cleanNewFlightDesc
                         );
                         );
 
 
@@ -1725,7 +1755,6 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                         {
                         {
                             airEntity.OriginalReservationId = originalNormalDbRow.Id;
                             airEntity.OriginalReservationId = originalNormalDbRow.Id;
                         }
                         }
-                        // 2. 如果库里没有(说明原正常单也是本次一起新提交进来的),则从内存追踪字典中溯源获取
                         else if (newCreatedNormalAirMap.TryGetValue(normalLookupKey, out int memorizedNormalId))
                         else if (newCreatedNormalAirMap.TryGetValue(normalLookupKey, out int memorizedNormalId))
                         {
                         {
                             airEntity.OriginalReservationId = memorizedNormalId;
                             airEntity.OriginalReservationId = memorizedNormalId;
@@ -1733,13 +1762,11 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     }
                     }
                     else
                     else
                     {
                     {
-                        airEntity.OriginalReservationId = 0; // 正常单默认为 0
+                        airEntity.OriginalReservationId = 0;
                     }
                     }
 
 
-                    // 写入数据库客票预约表
                     var airId = await _sqlSugar.Insertable(airEntity).ExecuteReturnIdentityAsync();
                     var airId = await _sqlSugar.Insertable(airEntity).ExecuteReturnIdentityAsync();
 
 
-                    // 如果当前新增的是正常购票单,将其登记到追踪字典,供后续可能存在的退票单进行指针勾连
                     if (airEntity.RecordType == 0)
                     if (airEntity.RecordType == 0)
                     {
                     {
                         if (!newCreatedNormalAirMap.ContainsKey(normalLookupKey))
                         if (!newCreatedNormalAirMap.ContainsKey(normalLookupKey))
@@ -1748,25 +1775,39 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                         }
                         }
                     }
                     }
 
 
-                    // 伴生创建信用卡账单
                     var cardEntity = _mapper.Map<Grp_CreditCardPayment>(item);
                     var cardEntity = _mapper.Map<Grp_CreditCardPayment>(item);
-                    cardEntity.DIId = groupId; cardEntity.CId = airId; cardEntity.CTable = 85; cardEntity.IsDel = 0; cardEntity.CreateUserId = currUserId; cardEntity.CreateTime = DateTime.Now;
+                    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.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-                    cardEntity.PayMoney = currentSegmentPrice; cardEntity.PayThenMoney = cardEntity.PayMoney;
+                    cardEntity.PayMoney = currentSegmentPrice;
+                    cardEntity.PayThenMoney = cardEntity.PayMoney;
 
 
                     decimal effectiveRate = cardEntity.DayRate > 0M ? cardEntity.DayRate : (item.DayRate > 0M ? item.DayRate : 1M);
                     decimal effectiveRate = cardEntity.DayRate > 0M ? cardEntity.DayRate : (item.DayRate > 0M ? item.DayRate : 1M);
                     cardEntity.RMBPrice = cardEntity.PayMoney * effectiveRate;
                     cardEntity.RMBPrice = cardEntity.PayMoney * effectiveRate;
 
 
+                    if (item.RecordType == 1)
+                    {
+                        cardEntity.IsAuditGM = 3;
+                        cardEntity.AuditGMOperate = 4;
+                        cardEntity.AuditGMDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                    }
+
                     var cardId = await _sqlSugar.Insertable(cardEntity).ExecuteReturnIdentityAsync();
                     var cardId = await _sqlSugar.Insertable(cardEntity).ExecuteReturnIdentityAsync();
 
 
-                    appPushBodyInfos.Add(new AppPushBodyInfo { OperationType = 1, AirTicketId = airId, CardId = cardId, CardCNYAmount = cardEntity.RMBPrice });
+                    if (cardEntity.PayMoney > 0)
+                    {
+                        appPushBodyInfos.Add(new AppPushBodyInfo { OperationType = 1, AirTicketId = airId, CardId = cardId, CardCNYAmount = cardEntity.RMBPrice });
+                    }
                 }
                 }
                 // -------------------- 分支 B:编辑覆盖 --------------------
                 // -------------------- 分支 B:编辑覆盖 --------------------
                 else
                 else
                 {
                 {
                     var dbCard = dbCardList.FirstOrDefault(x => x.CId == dbAir.Id);
                     var dbCard = dbCardList.FirstOrDefault(x => x.CId == dbAir.Id);
 
 
-                    // 财务状态风控拦截
                     if (!CanModify(dbCard))
                     if (!CanModify(dbCard))
                     {
                     {
                         var reason = dbCard?.IsPay == 1 ? "已付款" : dbCard?.IsAuditGM == 1 ? "已审核" : dbCard?.IsAuditGM == 3 ? "自动审核" : "禁止修改";
                         var reason = dbCard?.IsPay == 1 ? "已付款" : dbCard?.IsAuditGM == 1 ? "已审核" : dbCard?.IsAuditGM == 3 ? "自动审核" : "禁止修改";
@@ -1780,37 +1821,71 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                         continue;
                         continue;
                     }
                     }
 
 
-                    processedDbAirIds.Add(dbAir.Id);
-
                     var frontendClients = item.CustTicketInfos ?? new List<CustTicketInfo>();
                     var frontendClients = item.CustTicketInfos ?? new List<CustTicketInfo>();
                     dbAir.FlightsDescription = item.FlightsDescription;
                     dbAir.FlightsDescription = item.FlightsDescription;
                     dbAir.CType = item.CType;
                     dbAir.CType = item.CType;
                     dbAir.CustTicketInfos = frontendClients;
                     dbAir.CustTicketInfos = frontendClients;
-                    dbAir.ClientNum = frontendClients.Count;
-                    dbAir.ClientName = string.Join(",", frontendClients.Select(x => x.ClientId));
+                    dbAir.ClientNum = frontendClients.Select(x => x.ClientId).Distinct().Count();
+                    dbAir.ClientName = string.Join(",", frontendClients.Select(x => x.ClientId).Distinct());
                     dbAir.Price = currentSegmentPrice;
                     dbAir.Price = currentSegmentPrice;
                     dbAir.PriceDescription = item.PriceDescription;
                     dbAir.PriceDescription = item.PriceDescription;
-
-                    // 💡 编辑时强制校正库里的 RecordType,防止数据发生类型偏转
                     dbAir.RecordType = item.RecordType;
                     dbAir.RecordType = item.RecordType;
 
 
                     await _sqlSugar.Updateable(dbAir).ExecuteCommandAsync();
                     await _sqlSugar.Updateable(dbAir).ExecuteCommandAsync();
 
 
                     if (dbCard != null)
                     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.PayMoney = dbAir.Price;
+                        dbCard.PayThenMoney = dbCard.PayMoney;
+                        dbCard.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                        decimal effectiveRate = item.DayRate > 0M ? item.DayRate : 1M;
                         dbCard.RMBPrice = dbAir.Price * effectiveRate;
                         dbCard.RMBPrice = dbAir.Price * effectiveRate;
 
 
+                        dbCard.PayDId = item.PayDId;
+                        dbCard.ConsumptionPatterns = item.ConsumptionPatterns;
+                        dbCard.ConsumptionDate = item.ConsumptionDate;
+                        dbCard.CTDId = item.CTDId;
+                        dbCard.BankNo = item.BankNo;
+                        dbCard.CardholderName = item.CardholderName;
+                        dbCard.CompanyBankNo = item.CompanyBankNo;
+                        dbCard.OtherBankName = item.OtherBankName;
+                        dbCard.OtherSideNo = item.OtherSideNo;
+                        dbCard.Payee = item.Payee;
+                        dbCard.OrbitalPrivateTransfer = item.OrbitalPrivateTransfer;
+                        dbCard.Remark = item.Remark;
+
                         await _sqlSugar.Updateable(dbCard).ExecuteCommandAsync();
                         await _sqlSugar.Updateable(dbCard).ExecuteCommandAsync();
-                        appPushBodyInfos.Add(new AppPushBodyInfo { OperationType = 2, AirTicketId = dbAir.Id, CardId = dbCard.Id, CardCNYAmount = dbCard.RMBPrice });
+
+                        if (dbCard.PayMoney > 0)
+                        {
+                            appPushBodyInfos.Add(new AppPushBodyInfo { OperationType = 2, AirTicketId = dbAir.Id, CardId = dbCard.Id, CardCNYAmount = dbCard.RMBPrice });
+                        }
                     }
                     }
                 }
                 }
             }
             }
 
 
-            // =========================================================================
-            // 【集中清扫】
-            // =========================================================================
+            // 删除索引中剩余的未匹配记录(这些是数据库中多余的数据)
+            var unmatchedDbAirIds = dbAirIndex.Values
+                .Where(x => x != null && !devouredDbAirIds.Contains(x.Id))
+                .Select(x => x.Id)
+                .ToList();
+
+            if (unmatchedDbAirIds.Any())
+            {
+                var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+                await _sqlSugar.Updateable<Grp_AirTicketReservations>()
+                    .SetColumns(x => new Grp_AirTicketReservations { IsDel = 1, DeleteUserId = currUserId, DeleteTime = nowStr })
+                    .Where(x => unmatchedDbAirIds.Contains(x.Id))
+                    .ExecuteCommandAsync();
+
+                await _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                    .SetColumns(x => new Grp_CreditCardPayment { IsDel = 1, DeleteUserId = currUserId, DeleteTime = nowStr })
+                    .Where(x => unmatchedDbAirIds.Contains(x.CId) && x.CTable == 85)
+                    .ExecuteCommandAsync();
+            }
+
+            // 原有清扫逻辑继续执行
             if (allDevouredDbAirs.Any())
             if (allDevouredDbAirs.Any())
             {
             {
                 var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                 var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
@@ -1875,7 +1950,11 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
 
 
             foreach (var cabinType in cabinTypes)
             foreach (var cabinType in cabinTypes)
             {
             {
-                var cabinCustomers = air.CustTicketInfos?.Where(x => x.CType == cabinType).ToList() ?? new List<CustTicketInfo>();
+                var cabinCustomers = air.CustTicketInfos?
+                    .Where(x => x.CType == cabinType)
+                    .GroupBy(x => new { x.ClientId,x.CType })
+                    .Select(g => g.First())
+                    .ToList() ?? new List<CustTicketInfo>();
 
 
                 // 1. 【正常票过滤】:非退票,或者虽然标记了 IsRefund 但 RefundRecord 类根本不存在的归入正常票
                 // 1. 【正常票过滤】:非退票,或者虽然标记了 IsRefund 但 RefundRecord 类根本不存在的归入正常票
                 var normalCustomers = cabinCustomers;
                 var normalCustomers = cabinCustomers;
@@ -1883,24 +1962,18 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 if (currDbCabinTypes.Contains(cabinType) || normalCustomers.Any() || (air.CustTicketInfos == null || !air.CustTicketInfos.Any()))
                 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));
                     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
-                    });
+                    var clientIds = normalCustomers.Select(x => x.ClientId).ToList();
+                    var airInfo = _mapper.Map<AirTicketFeeOpInfo>(air);
+                    airInfo.DIId = groupId;
+                    airInfo.CType = cabinType;
+                    airInfo.RecordType = 0;
+                    airInfo.ClientNum = normalCustomers.Count;
+                    airInfo.ClientName = string.Join(",", clientIds);
+                    airInfo.CustTicketInfos = normalCustomers;
+                    airInfo.ClientNameIds = clientIds;
+                    airInfo.Price = normalPrice;
+                    airInfo.PayMoney = normalPrice;
+                    result.Add(airInfo);
                 }
                 }
 
 
                 // 2. 【退票信息拆分】:严格验证 IsRefund 为 true 且 RefundRecord 类非空
                 // 2. 【退票信息拆分】:严格验证 IsRefund 为 true 且 RefundRecord 类非空
@@ -1913,25 +1986,23 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 {
                 {
                     var refundCustomers = refundGroup.ToList();
                     var refundCustomers = refundGroup.ToList();
                     var refundAmount = refundCustomers.Sum(x => (x.RefundRecord?.RefundAmount ?? 0M) + (x.RefundRecord?.NonRefundableTax ?? 0M));
                     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
-                    });
+                    var clientIds = refundCustomers.Select(x => x.ClientId).ToList();
+
+                    var airInfo = _mapper.Map<AirTicketFeeOpInfo>(air);
+                    airInfo.DIId = groupId;
+                    airInfo.CType = cabinType;
+                    airInfo.RecordType = 1;
+                    airInfo.CTDId = refundGroup.Key;
+                    airInfo.ClientNum = clientIds.Count;
+                    airInfo.ClientName = string.Join(",", clientIds);
+                    airInfo.CustTicketInfos = refundCustomers;
+                    airInfo.ClientNameIds = clientIds;
+                    airInfo.Price = refundAmount;
+                    airInfo.PayMoney = refundAmount;
+                    airInfo.AuditGMOperate = 4; // 退票信息默认自动审核,前端不允许修改;
+                    airInfo.IsAuditGM = 3;      // 退票信息默认自动审核,前端不允许修改;
+                    airInfo.AuditGMDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                    result.Add(airInfo);
                 }
                 }
             }
             }
         }
         }
@@ -2183,7 +2254,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
             if (refundRecord == null)
             if (refundRecord == null)
             {
             {
                 _sqlSugar.RollbackTran();
                 _sqlSugar.RollbackTran();
-                return JsonView.Fail("未找到对应的退票记录!");
+                return JsonView.Fail(300, "未找到对应的退票记录!");
             }
             }
 
 
             // 3. 校验退票财务账目状态
             // 3. 校验退票财务账目状态
@@ -2192,17 +2263,17 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 .Where(x => x.DIId == dto.GroupId && x.CId == refundRecord.Id && x.CTable == 85 && x.IsDel == 0)
                 .Where(x => x.DIId == dto.GroupId && x.CId == refundRecord.Id && x.CTable == 85 && x.IsDel == 0)
                 .FirstAsync();
                 .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},财务单据已锁定不可逆向调整!");
-                }
-            }
+            //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. 解析退票人员列表存在性
             // 4. 解析退票人员列表存在性
             var allRefundClients = refundRecord.CustTicketInfos ?? new List<CustTicketInfo>();
             var allRefundClients = refundRecord.CustTicketInfos ?? new List<CustTicketInfo>();
@@ -2473,7 +2544,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
     }
     }
 
 
     /// <summary>
     /// <summary>
-    /// 机票费用报表数据源(全量解析不含时间隔离版)
+    /// 机票费用报表数据源
     /// </summary>
     /// </summary>
     /// <param name="dto"></param>
     /// <param name="dto"></param>
     /// <returns></returns>
     /// <returns></returns>
@@ -2487,7 +2558,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                                   Left Join Sys_SetData sd2 On sd2.Id = atr.PreCurrency                                          
                                   Left Join Sys_SetData sd2 On sd2.Id = atr.PreCurrency                                          
                                   Left Join Sys_SetData sd3 On sd3.Id = atr.Currency                                          
                                   Left Join Sys_SetData sd3 On sd3.Id = atr.Currency                                          
                                   left join Sys_SetData sd4 On sd4.Id = ccp.PayDId                                           
                                   left join Sys_SetData sd4 On sd4.Id = ccp.PayDId                                           
-                                  Where atr.IsDel = 0 And atr.DIId={0}                                          
+                                  Where atr.IsDel = 0 And  atr.ClientNum > 0 And atr.DIId={0}                                          
                                   Order By atr.FlightsDate Asc", dto.DiId);
                                   Order By atr.FlightsDate Asc", dto.DiId);
         var airInfoList = await _sqlSugar.SqlQueryable<DeriveInfo>(sql).ToListAsync();
         var airInfoList = await _sqlSugar.SqlQueryable<DeriveInfo>(sql).ToListAsync();
 
 
@@ -2534,8 +2605,17 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
 
 
                 #endregion
                 #endregion
 
 
+                // 退票时更改金额为负数
+                if (item.RecordType == 1) item.Price = -Math.Abs(item.Price);
+
                 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<int>();
                 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<int>();
                 var clientNames = clientNameList.Where(c => clientIds.Contains(c.Id)).Select(c => $"{c.FirstName}{c.LastName}").ToList();
                 var clientNames = clientNameList.Where(c => clientIds.Contains(c.Id)).Select(c => $"{c.FirstName}{c.LastName}").ToList();
+
+                if (clientIds.Contains(-1))
+                {
+                    clientNames.Add("行程单");
+                }
+
                 item.ClientName = string.Join("、", clientNames);
                 item.ClientName = string.Join("、", clientNames);
 
 
                 // 新数据单独处理
                 // 新数据单独处理
@@ -2549,7 +2629,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 }
                 }
 
 
                 // 拼接航段城市串 (如:PEK ➔ DXB / DXB ➔ LHR)
                 // 拼接航段城市串 (如:PEK ➔ DXB / DXB ➔ LHR)
-                item.FlightsCity = string.Join(@" / ", flightInfoList.Select(f =>
+                item.FlightsCity = string.Join(@"/", flightInfoList.Select(f =>
                 {
                 {
                     var dept = f.DepartureAirport?.Trim().ToUpper() ?? "???";
                     var dept = f.DepartureAirport?.Trim().ToUpper() ?? "???";
                     var arr = f.ArrivalAirport?.Trim().ToUpper() ?? "???";
                     var arr = f.ArrivalAirport?.Trim().ToUpper() ?? "???";
@@ -2557,7 +2637,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 }));
                 }));
 
 
                 // 提取首段航班号作为主航班号展现
                 // 提取首段航班号作为主航班号展现
-                item.FlightsCode = flightInfoList.FirstOrDefault()?.FlightNumber ?? "未知航班";
+                item.FlightsCode = string.Join("、", flightInfoList.Select(f => f.FlightNumber?.Trim().ToUpper() ?? "未知航班"));
 
 
                 // 附加服务附加名称处理
                 // 附加服务附加名称处理
                 var serviceTypes = item.CustTicketInfos?
                 var serviceTypes = item.CustTicketInfos?
@@ -2578,12 +2658,12 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     item.CTypeName += $"(含{string.Join("、", serviceTypes)}等服务)";
                     item.CTypeName += $"(含{string.Join("、", serviceTypes)}等服务)";
                 }
                 }
 
 
-                // 金额单价防呆计算
-                if (item.RecordType == 0)
-                {
+                //// 金额单价防呆计算
+                //if (item.RecordType == 0)
+                //{
                     // 防止历史脏数据中乘机人数为 0 导致除以零系统崩溃
                     // 防止历史脏数据中乘机人数为 0 导致除以零系统崩溃
                     item.PrePrice = item.ClientNum > 0 ? (item.Price / item.ClientNum) : item.Price;
                     item.PrePrice = item.ClientNum > 0 ? (item.Price / item.ClientNum) : item.Price;
-                }
+                //}
             }
             }
 
 
             // 4. 捞取团组上下文与操作员名称
             // 4. 捞取团组上下文与操作员名称
@@ -2610,22 +2690,88 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
         public string Opeator { get; set; }
         public string Opeator { get; set; }
     }
     }
 
 
-
-    public async Task<Result> TripListAsync(ItineraryAirTicketResDto dto)
+    /// <summary>
+    /// 行程单列表数据源
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<JsonView<List<AirTicketReservationsView>>> 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<AirTicketReservationsView> _AirTicketReservations = _sqlSugar.SqlQueryable<AirTicketReservationsView>(sql).ToList();
-        if (_AirTicketReservations.Count == 0)
+        string sql = string.Format(@"select a.*,b.IsAuditGM,b.payee,c.Name as 'CTypeName',d.Name as 'CurrencyStr' from Grp_AirTicketReservations a
+inner join Grp_CreditCardPayment b on a.Id = b.CId and b.CTable = 85 and a.DIId = b.DIId
+left join Sys_SetData c on a.cType = c.Id
+left join Sys_SetData d on a.Currency = d.Id
+where a.isdel = 0 and a.DIId = {0} and a.RecordType = 0
+Order By a.CreateTime desc", dto.DiId);
+        var airInfoList = await _sqlSugar.SqlQueryable<AirTicketReservationsView>(sql).ToListAsync();
+        if (!airInfoList.Any())
         {
         {
-            return new Result() { Code = -1, Msg = "暂无数据", Data = null };
+            return JsonView<List<AirTicketReservationsView>>.Fail("暂无数据");
         }
         }
-        else
+
+        // 预捞服务类型基础数据
+        var serviceTypeList = await _sqlSugar.Queryable<Sys_SetData>()
+            .Where(x => x.IsDel == 0 && x.STid == 139)
+            .Select(x => new { x.Id, x.Name })
+            .ToListAsync();
+
+        // 预捞客户信息并全量执行内存解密
+        var clientDatas = await _sqlSugar.Queryable<Grp_TourClientList>()
+            .LeftJoin<Crm_DeleClient>((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)
         {
         {
-            return new Result() { Code = 0, Msg = "查询成功", Data = _AirTicketReservations };
+            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<int>();
+            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 ?? "未知航班";
+
+            // 金额单价防呆计算
+            if (item.RecordType == 0)
+            {
+                // 防止历史脏数据中乘机人数为 0 导致除以零系统崩溃
+                item.PrePrice = item.ClientNum > 0 ? (item.Price / item.ClientNum) : item.Price;
+            }
+
+            // FlightInfoList
+            item.FlightInfoList = FlightParser.ParseFlights(item.FlightDescription);
+
         }
         }
+
+        return JsonView<List<AirTicketReservationsView>>.Success("查询成功", airInfoList);
     }
     }
 
 
     #endregion
     #endregion