Przeglądaj źródła

1、费用审核 -> 机票模块 名称更改
2、费用审核 -> 新增IOS联调接口
3、机票费用 -> 用户删除、取消退票

Lyyyi 1 dzień temu
rodzic
commit
c6c0887cba

+ 1 - 1
OASystem/OASystem.Api/Controllers/FinancialController.cs

@@ -1312,7 +1312,7 @@ namespace OASystem.API.Controllers
                                 SetCells(ChildTable1, doc, rowIndex, 0, item.Place);
                                 SetCells(ChildTable1, doc, rowIndex, 1, $"{days}天");
                                 SetCells(ChildTable1, doc, rowIndex, 2, $"{item.Cost:0.00}{currencyCode}/天");
-                                SetCells(ChildTable1, doc, rowIndex, 3, "汇率" + currencyRate);
+                                SetCells(ChildTable1, doc, rowIndex, 3, $"汇率{currencyRate:0.0000}");
 
                                 decimal currSubTotal = Math.Round(days * item.Cost * currencyRate, 2);
                                 SetCells(ChildTable1, doc, rowIndex, 4, $"CNY {currSubTotal:0.00}");

+ 133 - 43
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -11590,6 +11590,19 @@ FROM
                                 string PriceDescription = jpRes.PriceDescription;
                                 _detail.PriceMsgContent = "航班号:" + jpRes.FlightsCode + "<br/>城市A-B:" + jpRes.FlightsCity + "<br/>航班描述:" + FlightsDescription.Replace("\r\n", "<br />") + "<br/>" + "价格描述:" + PriceDescription;
                                 _detail.PriceNameContent = "(" + jpRes.FlightsCode + ")";
+
+                                // 处理 退票 其他 等费用类型显示在 航班号后面
+                                var feeTypeName = initDatas.Where(x => x.Id == jpRes.CType).Select(x => x.Name).FirstOrDefault();
+                                var filterTypeNames = new List<string>() {
+                                    "退票",
+                                    "行程单打印费",
+                                    "选座费",
+                                    "其他",
+                                };
+                                if (filterTypeNames.Contains(feeTypeName))
+                                {
+                                    _detail.PriceNameContent += $" - {feeTypeName}";
+                                }
                             }
                             break;
                         case 98://其他款项
@@ -13248,7 +13261,7 @@ FROM
         /// <returns></returns>
         [HttpGet("/api/Groups/airTicket/base-data/{userId}")]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
-        public async Task<IActionResult> GetAirTicketCostBasicData(int userId)
+        public async Task<IActionResult> AirTicketCostBasicData(int userId)
         {
             return Ok(await _airTicketResRep.BasicDataAsync(userId));
         }
@@ -13260,7 +13273,7 @@ FROM
         /// <returns></returns>
         [HttpGet("/api/Groups/airTicket/info/{userId}/{groupId}")]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
-        public async Task<IActionResult> GetAirTicketCostInfo(int userId, int groupId)
+        public async Task<IActionResult> AirTicketCostInfo(int userId, int groupId)
         {
             return Ok(await _airTicketResRep.InfoAsync(userId, groupId));
         }
@@ -13273,7 +13286,7 @@ FROM
         [HttpPost("/api/Groups/airTicket/save")]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status400BadRequest)]
-        public async Task<IActionResult> GetAirTicketCostInfo([FromBody]AirTicketFeeSaveDto dto)
+        public async Task<IActionResult> AirTicketCostSave([FromBody]AirTicketFeeSaveDto dto)
         {
             #region 参数验证
             if (dto.CurrUserId < 1) return Ok(JsonView(false, "请输入有效的CurrUserId"));
@@ -13293,10 +13306,12 @@ FROM
                     return Ok(JsonView(false, $"第{index}段行程,请输入有效的航段基础信息"));
                 }
 
-                if (string.IsNullOrEmpty(airTicketFeeInfo.ClientName) || airTicketFeeInfo.ClientNameIds == null || airTicketFeeInfo.ClientNameIds.Count < 1)
+                if (airTicketFeeInfo.ClientNameIds == null || airTicketFeeInfo.ClientNameIds.Count < 1)
                 {
                     return Ok(JsonView(false, $"第{index}段行程,请输入有效的客户信息"));
                 }
+
+                airTicketFeeInfo.ClientName = string.Join(",", airTicketFeeInfo.ClientNameIds);
             }
 
             #endregion
@@ -13352,6 +13367,43 @@ FROM
             return Ok(JsonView(true,result.Msg));
         }
 
+        /// <summary>
+        /// 机票费用录入 - 2026版
+        /// 用户取消退票
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("/api/Groups/airTicket/client/cancelRefund")]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> AirTicketCostTripUserDel([FromBody] CancelClientRefundDto dto)
+        {
+            if (dto.GroupId < 1) return Ok(JsonView(false, "参数错误:团组ID不能为空"));
+            if (string.IsNullOrEmpty(dto.FlightsDescription)) return Ok(JsonView(false, "参数错误:行程代码不能为空"));
+            if (dto.CType < 1) return Ok(JsonView(false, "参数错误:舱位类型不能为空"));
+            if (dto.ClientId < 1) return Ok(JsonView(false, "参数错误:客户ID不能为空"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "参数错误:用户ID不能为空"));
+
+            return Ok(await _airTicketResRep.CancelClientRefundAsync(dto));
+        }
+
+        /// <summary>
+        /// 机票费用录入 - 2026版
+        /// 行程内删除用户
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("/api/Groups/airTicket/client/del")]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> AirTicketCostTripUserDel([FromBody] AirTicketCostTripUserDelDto dto)
+        {
+            if (dto.GroupId < 1) return Ok(JsonView(false, "参数错误:团组ID不能为空"));
+            if (string.IsNullOrEmpty(dto.FlightsDescription)) return Ok(JsonView(false, "参数错误:行程代码不能为空"));
+            if (dto.CType < 1) return Ok(JsonView(false, "参数错误:舱位类型不能为空"));
+            if (dto.ClientId < 1) return Ok(JsonView(false, "参数错误:客户ID不能为空"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "参数错误:用户ID不能为空"));
+
+            return Ok(await _airTicketResRep.TripUserDeleteAsync(dto));
+        }
+
+
         /// <summary>
         /// 机票费用录入 - 2026版
         /// del
@@ -13359,7 +13411,7 @@ FROM
         /// <returns></returns>
         [HttpPost("/api/Groups/airTicket/del")]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
-        public async Task<IActionResult> GetAirTicketCostInfo([FromBody]DelBaseDto dto)
+        public async Task<IActionResult> AirTicketCostDel([FromBody]DelBaseDto dto)
         {
             if (dto.DeleteUserId < 1) return Ok(JsonView(false, "参数错误:用户ID不能为空"));
             if (dto.Id < 1) return Ok(JsonView(false, "参数错误:记录ID不能为空"));
@@ -16317,72 +16369,110 @@ FROM
             //接送机
             if (dto.APTFeeDetails?.Any() == true)
             {
-                var ovIds = dto.APTFeeDetails.Select(x => x.OvId).ToList();
+                if (dto.APTFeeDetails.Sum(x => x.Quote) > 0)
+                {
+                    var ovIds = dto.APTFeeDetails.Select(x => x.OvId).ToList();
 
-                var areas = await _sqlSugar.Queryable<Res_OverseaVehicleTypePrice>()
-                    .LeftJoin<Res_OverseaVehicle>((ovtp, ov) => ovtp.OvId == ov.Id)
-                    .Where((ovtp, ov) => ovtp.IsDel == 0 && ovIds.Contains(ovtp.Id))
-                    .Select((ovtp, ov) => new
-                    {
-                        ovtp.Id,
-                        ov.CountryName,
-                        ov.CityName
-                    }).ToListAsync();
+                    var areas = await _sqlSugar.Queryable<Res_OverseaVehicleTypePrice>()
+                        .LeftJoin<Res_OverseaVehicle>((ovtp, ov) => ovtp.OvId == ov.Id)
+                        .Where((ovtp, ov) => ovtp.IsDel == 0 && ovIds.Contains(ovtp.Id))
+                        .Select((ovtp, ov) => new
+                        {
+                            ovtp.Id,
+                            ov.CountryName,
+                            ov.CityName
+                        }).ToListAsync();
 
-                var items = dto.APTFeeDetails
-                    .Select(item =>
-                    {
-                        var areaInfo = areas.FirstOrDefault(x => x.Id == item.OvId);
-                        if (areaInfo == null) return null;
+                    var items = dto.APTFeeDetails
+                        .Select(item =>
+                        {
+                            var areaInfo = areas.FirstOrDefault(x => x.Id == item.OvId);
+                            if (areaInfo == null) return null;
 
-                        return areaInfo.CountryName == areaInfo.CityName
-                            ? $"{areaInfo.CountryName}({item.Quote:#0.00} CNY)"
-                            : $"{areaInfo.CountryName}-{areaInfo.CityName}({item.Quote:#0.00} CNY)";
-                    })
-                    .Where(item => item != null)
-                    .ToList();
+                            return areaInfo.CountryName == areaInfo.CityName
+                                ? $"{areaInfo.CountryName}({item.Quote:#0.00} CNY)"
+                                : $"{areaInfo.CountryName}-{areaInfo.CityName}({item.Quote:#0.00} CNY)";
+                        })
+                        .Where(item => item != null)
+                        .ToList();
 
-                label.AppendLine($"接送机({string.Join("、", items)});");
+                    label.AppendLine($"接送机({string.Join("、", items)});");
+                }
             }
 
             //拉车
             if (dto.PullCartFeeDetails?.Any() == true)
             {
-                var feeLabel = string.Join("、",
+                if (dto.PullCartFeeDetails.Sum(x => x.Quote) > 0)
+                {
+                    var feeLabel = string.Join("、",
                     dto.PullCartFeeDetails.Select(x =>
                         $"{x.Quote:#0.00} CNY"
                     ));
 
-                label.AppendLine($"拉车({feeLabel});");
+                    label.AppendLine($"拉车({feeLabel});");
+                }
             }
 
             //火车
             if (dto.TrainFeeDetails?.Any() == true)
             {
-                var feeLabel = string.Join("、",
-                     dto.TrainFeeDetails.Select(x =>
-                         $"{x.Quote:#0.00} CNY"
-                     ));
+                if (dto.TrainFeeDetails.Sum(x => x.Quote) > 0)
+                {
+                    var feeLabel = string.Join("、",
+                    dto.TrainFeeDetails.Select(x =>
+                        $"{x.Quote:#0.00} CNY"
+                    ));
 
-                label.AppendLine($"火车票({feeLabel});");
+                    label.AppendLine($"火车票({feeLabel});");
+                }
             }
 
             //城市机票
+            decimal jjcTotal = 0.00m,
+                    gwcTotal = 0.00m,
+                    tdcTotal = 0.00m;
             if (dto.CityAirTicketFeeDetails?.Any() == true)
             {
-                var feeLabel = string.Join("、",
-                     dto.CityAirTicketFeeDetails.Select(x =>
-                         $"{x.Quote:#0.00} CNY"
-                     ));
+                if (dto.CityAirTicketFeeDetails.Sum(x => x.Quote) > 0)
+                {
+                    //var feeLabel = string.Join("、",
+                    //     dto.CityAirTicketFeeDetails.Select(x =>
+                    //         $"{x.Quote:#0.00} CNY"
+                    //     ));
+                    //label.AppendLine($"城市机票({feeLabel});");
 
-                label.AppendLine($"城市机票({feeLabel});");
+                    label.Append($"城市机票(");
+                    var jjcInfo = dto.CityAirTicketFeeDetails.Where(x => x.Remark.contains("经济舱")).FirstOrDefault();
+                    if (jjcInfo != null)
+                    {
+                        jjcTotal = jjcInfo.Cost * jjcInfo.Coefficient;
+                        if (jjcTotal > 0) label.Append($"经济舱:{jjcTotal:0.00} CNY");
+                    }
+
+                    var gwcInfo = dto.CityAirTicketFeeDetails.Where(x => x.Remark.contains("公务舱")).FirstOrDefault();
+                    if (gwcInfo != null)
+                    {
+                        gwcTotal = gwcInfo.Cost * gwcInfo.Coefficient;
+                        if (gwcTotal > 0) label.Append($"、公务舱:{gwcTotal:0.00} CNY");
+                    }
+
+                    var tdcInfo = dto.CityAirTicketFeeDetails.Where(x => x.Remark.contains("头等舱")).FirstOrDefault();
+                    if (tdcInfo != null)
+                    {
+                        tdcTotal = tdcInfo.Cost * tdcInfo.Coefficient;
+                        if (tdcTotal > 0) label.Append($"、头等舱:{tdcTotal:0.00} CNY");
+                    }
+
+                    label.Append($");");
+                }
             }
 
             //费用
-            decimal quote = (dto.APTFeeTotal + dto.PullCartFeeTotal + dto.TrainFeeTotal + dto.CityAirTicketFeeTotal).ConvertToDecimal1();
-            decimal jjcQuote = quote;
-            decimal gwcQuote = quote;
-            decimal tdcQuote = quote;
+            decimal quote = (dto.APTFeeTotal + dto.PullCartFeeTotal + dto.TrainFeeTotal).ConvertToDecimal1();
+            decimal jjcQuote = quote + jjcTotal;
+            decimal gwcQuote = quote + gwcTotal;
+            decimal tdcQuote = quote + tdcTotal;
 
             var view = new
             {

+ 91 - 155
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -1712,173 +1712,109 @@ namespace OASystem.API.OAMethodLib
         #endregion
 
         /// <summary>
-        /// 混元提示词构建(高度定制化,适配商邀资料场景)
+        /// 构建混元 AI 商邀资料检索提示词(高度定制化、生产级)。
         /// </summary>
-        /// <param name="tasks">包含国家及 Count 数量的任务列表</param>
-        /// <param name="countryTasks"></param>
-        /// <param name="entryInfo">包含规则基础信息</param>
-        /// <returns>最终构建的 System Prompt 字符串</returns>
-        public static string BuildHunyuanPrompt(List<CountryAIPormptInfo> tasks, List<AITaskItem> countryTasks, EntryInfo entryInfo)
+        public static string BuildHunyuanPrompt(
+            List<CountryAIPormptInfo> tasks,
+            List<AITaskItem> countryTasks,
+            EntryInfo entryInfo)
         {
-            // 行业信息,用于提示词硬约束
-            string industryEnum = string.Join("、", IndustryTree.Build().Select(x => x.NameCn).ToList());
+            var today = DateTime.Now.ToString("yyyy-MM-dd");
 
             var businessConstraints = new StringBuilder();
-            if (entryInfo.Industries.Any()) businessConstraints.AppendLine($" - 行业信息: {string.Join("、", entryInfo.Industries)}");
-            if (entryInfo.ScaleTypes.Any()) businessConstraints.AppendLine($" - 单位规模: {string.Join("、", entryInfo.ScaleTypes)}");
-            if (entryInfo.IsBackground) businessConstraints.AppendLine($" - 单位是否包含华人背景: 是");
-            if (!string.IsNullOrEmpty(entryInfo.OrgLevel) && !entryInfo.OrgLevel.Equals("全部")) businessConstraints.AppendLine($" - 公司层级: {entryInfo.OrgLevel}");
-            if (!string.IsNullOrEmpty(entryInfo.OtherConstraints)) businessConstraints.AppendLine($" - 其他规则: {entryInfo.OtherConstraints}");
-            if (businessConstraints == null || businessConstraints.Length == 0) businessConstraints.AppendLine("无");
+            if (entryInfo.Industries.Any())
+                businessConstraints.AppendLine($" - 行业信息: {string.Join("、", entryInfo.Industries)}");
+            if (entryInfo.ScaleTypes.Any())
+                businessConstraints.AppendLine($" - 单位规模: {string.Join("、", entryInfo.ScaleTypes)}");
+            if (entryInfo.IsBackground)
+                businessConstraints.AppendLine(" - 单位是否包含华人背景: 是");
+            if (!string.IsNullOrEmpty(entryInfo.OrgLevel) && entryInfo.OrgLevel != "全部")
+                businessConstraints.AppendLine($" - 公司层级: {entryInfo.OrgLevel}");
+            if (!string.IsNullOrEmpty(entryInfo.OtherConstraints))
+                businessConstraints.AppendLine($" - 其他规则: {entryInfo.OtherConstraints}");
 
             return $@"
 # [SYSTEM_ROLE]
-顶级商务咨询顾问。精通全球实时经贸情报。输出 JSON 必须 100% 兼容强类型反序列化。遵循:无存证不输出、官网优先、AB管道证伪、零虚构熔断。
+你是精通全球实时经贸情报的【顶级商务咨询顾问】。
+你具备资深 .NET 6 架构思维,输出 JSON 必须 100% 兼容强类型反序列化。遵循“无存证不输出、官网优先溯源、AB管道证伪、零虚构熔断”的最高数据纯度准则。
 
-# [CONTEXT]
-CurrentDate: {DateTime.Now:yyyy-MM-dd}
-OriginUnit: {entryInfo.OriginUnit}
-BusinessConstraints: {businessConstraints}
-CountryTasks: {JsonConvert.SerializeObject(countryTasks)}
-Tasks: {JsonConvert.SerializeObject(tasks)}
-
-# [REALITY_CHECK_RULES]
-## 1. 企业存在性三位一体核验(多源公证)
-必须同时满足,且每项至少通过 **两种独立、可公证的来源** 交叉验证:
-- 官网可访问且业务匹配(来源:企业官网 + 工商/协会/权威媒体)
-- 管道A:目标国工商/证券/经商处备案(来源:政府公示 + 权威平台)
-- 管道B:LinkedIn(官方认证页)/Bloomberg/Crunchbase 近3年动态(来源:平台 + 媒体)
-→ 任一核心字段无法通过多源公证 → 整条作废
-
-### 可接受的公证件来源(任选 ≥ 2):
-- 政府/监管:工商注册、证券交易所、驻华经商处
-- 国际平台:LinkedIn(认证页)、Bloomberg、Crunchbase
-- 行业协会/展会:国际农业展、种业协会名录
-- 主流媒体:Reuters、Bloomberg、当地财经媒体
-- 企业披露:官网 About / Contact / Imprint
-
-### 不可作为公证来源:
-- Wikipedia、论坛、博客、聚合导航站、模型推断
-
-## 2. 实时可达性检测
-SiteUrl / PostUrl 必须通过:
-- DNS 成功
-- HTTP 200,响应 < 5s
-- HTML ≥ 500 字符
-- 不含:404/not found/page not found/under maintenance/coming soon/site is temporarily down
-→ SiteUrl 不通过 → SiteUrl 返回 """"
-→ PostUrl 不通过 → PostUrl 返回 []
-
-## 3. 软 404 熔断
-URL 含以下关键词 → 死链:
-404 Not Found, Page Not Found, Oops, Seite nicht gefunden, foundpage, 链接不存在
-→ SiteUrl 命中 → SiteUrl 返回 """"
-→ PostUrl 命中 → PostUrl 返回 []
-
-## 4. 属地物理地址强校验
-- 地址必须在 CountryTasks 指定国家
-- 与官网 Contact/Imprint/About Us 完全一致
-- Google Maps / OpenStreetMap 可验证真实建筑
-- 非虚拟办公室 / P.O. Box / 商业中心
-→ 不通过 → 整条作废
-
-## 5. 英文命名清洗
-NameEn 严禁包含:
-LTD, LLC, JSC, AO, TOO, IP, PLC, INC, CORP, GMBH, AG, BV, NV 等
-→ 仅保留核心品牌名,与官网首页 Title 一致
-→ 清洗后为空或通用词 → 整条作废
-
-## 6. 链接健康评分 ≥ 90
-DNS(20)+HTTP200(30)+HTML≥500(20)+无软死链(20)+归属企业一致(10)
-→ <90 → 对应 URL 直接丢弃
-
-## 7. 官网地址刚性约束(Zero-Guess Rule)
-- SiteUrl 必须是企业在互联网上真实存在、可直接访问的官网地址;
-- AI 严禁对网址进行任何形式的拼接、补全、改写或推断;
-- 仅允许原样返回企业在官网、工商备案或 AB 管道中公开披露的地址;
-- 严禁添加国别编码、语言路径、查询参数、目录层级或任何模型生成的路径片段;
-- 若模型无法确认某个网址的真实存在性,则该字段必须返回空字符串。
-
-## 8. 零容忍熔断
-触发任一即终止条目:
-- 官网不可达
-- 地址无法地图验证
-- 企业无法三位一体证伪
-- 任何字段推测性补全
-→ 严禁为填满配额降低标准
-
-## 9. 时效约束
-• 所有数据必须是 {DateTime.Now.Year} 当天检测结果
-• 禁止缓存 / 历史快照 / 旧索引
-
-# [BUSINESS_CONSTRAINTS]
-- 零虚构:禁止编造企业/人名/链接/动态
-- Industry 严格锁定
-- Scale 严格匹配
-- NameEn 执行命名清洗
-
-# [QUOTA]
-- 有效条目不足允许 < 配额
-- 禁止凑数,禁止降级标准
-
-# [EXECUTION_PIPELINE]
-严格顺序执行,禁止跳步、禁止提前生成企业名称或 JSON:
-1. 解析 CountryTasks + BusinessConstraints
-2. 检索候选企业(仅目标国)
-3. 多源公证核验(官网 + 管道A + 管道B + 交叉验证)
-   - 企业名称、官网、地址、联系方式必须通过 ≥2 个独立来源验证
-4. 官网实时可达性检测(DNS/HTTP/HTML)
-5. 软 404 与链接健康评分 ≥ 90
-6. 属地物理地址强校验(地图实体验证)
-7. 英文命名清洗
-8. 提取字段(官网优先,AB 管道补位)
-   - SiteUrl 必须原样取自企业公开信息,禁止任何形式的拼接、改写或补全;
-   - 拿不准就留空,严禁生成“看起来合理”的地址。
-9. 最终零容忍熔断检查
-10. 输出纯净 JSON 或失败审计
-
-# [FAILURE_AUDIT_HOOK]
-若最终输出为空数组 [],必须改用以下格式输出(仅字符串,不含 JSON):
-AuditReason::StepX::{{失败原因}}
-
-示例:
-AuditReason::Step3::多源公证失败,企业名称仅出现在单一来源
-AuditReason::Step3::多源公证失败,官网地址无法通过工商与平台交叉验证
-AuditReason::Step4::官网不可达,DNS解析失败
-AuditReason::Step7::官网地址为模型拼接,已熔断
-
-# [OUTPUT_SCHEMA]
-PascalCase,禁止新增字段:
+# [CONTEXT_ANALYSIS]
+• 当前核查基准时间 (CurrentDate): {today} (锁定动态年份为绝对时空锚点)
+• 发起单位 (OriginUnit): {entryInfo.OriginUnit}
+• 业务硬性约束 (BusinessConstraints): {businessConstraints}
+• 核心驱动配置 (CountryTasks): {JsonConvert.SerializeObject(countryTasks)}
+• 任务配额分配 (Tasks): {JsonConvert.SerializeObject(tasks)}
+
+# [REALITY_CHECK_RULES - 严禁虚构与存证审计标准]
+
+## 1. 企业存在性“三位一体”核验 (Anti-Hallucination)
+- **核心原则**:严禁推测、编造、组合不存在的企业名称。必须严格按照 `CountryTasks` 定义的区域执行检索。
+- **验证链条**:
+  1. **官网验证**:SiteUrl 必须真实可访问,且内容与企业业务高度吻合。
+  2. **管道 A (官方公示)**:必须在目标国工商注册局、证监会或经商处有备案。
+  3. **管道 B (公正存证)**:必须在 LinkedIn (官方认证页)、Bloomberg 或 Crunchbase 有实时动态。
+- **熔断判定**:若以上三项均无法交叉证实企业真实存在性,该条目必须立即废弃。
+
+## 2. 软 404 与“FoundPage”假死内容熔断
+- **实质内容审计**:即便 URL 响应 200,若页面包含以下特征,必须判定为**死链**并熔断:
+  - **特征词**:`404 Not Found`, `Page Not Found`, `Oops`, `Seite nicht gefunden`, `foundpage`, `链接不存在`.
+- **主站熔断**:若 `SiteUrl` 触碰报错特征,该企业条目整体作废;若仅 `PostUrl` 触碰,则动态字段返回 `[]`。
+
+## 3. 信息溯源:官网优先与 AB 管道补位
+- **首要来源**:新闻 (PostUrl)、联系人 (Contact) 与邮件 (Email) 必须优先从企业官网提取。
+- **补位机制**:仅当官网无法获取联系信息时,允许从 AB 管道(管道B认证页)提取 {DateTime.Now.AddYears(-3).Year}-{DateTime.Now.Year} 年间的实时活跃数据。
+
+## 4. 属地物理地址强校验 (GEO_ADDRESS_STRICTNESS)
+- **物理属地锁定原则**:企业必须是在目标国家**实际运营、注册并拥有实体办公场所**的主体,严禁接受虚拟办公室、共享注册地址或仅用于税务/法律注册的空壳地址。
+- **地址真实性审计流程**:
+  1. **地理一致性校验**:`Address` 字段必须位于 `CountryTasks` 指定的国家境内,且与官网“Contact / Imprint / About Us”页面披露的实体地址完全一致。
+  2. **地图实体验证(AB 管道)**:必须通过 Google Maps / OpenStreetMap 等权威地理服务验证该地址对应真实建筑,且建筑内确实挂有该企业标识(或街景证据)。
+  3. **虚拟地址熔断**:若地址被识别为虚拟办公、信箱服务(P.O. Box)或商业中心,该条目**整条作废**。
+- **输出规范**:`Address` 必须返回**可直接复制粘贴到 Google Maps、百度地图、Apple 地图等主流地图服务中进行搜索和导航的完整物理地址**。格式要求:街道地址 + 城市 + 州/省 + 邮政编码 + 国家。地址中必须包含可被地图服务准确识别的**邮政编码**。
+
+# [BUSINESS_CONSTRAINTS_SCHEMA]
+- **零虚构原则**:绝对禁止编造任何不存在的企业、人名、链接或动态。
+- **行业约束**:Industry 必须严格锁定在业务定义的范围内,严禁跨类延伸。
+- **规模匹配**:Scale 必须严格匹配“单位规模”集合定义。
+- **英文命名清洗规则 (NAMING_CONVENTION_EN_CLEAN)**:在 `NameEn` 字段中,**严禁出现任何法律实体后缀标识**。包括但不限于:`PTY LTD`, `LTD`, `LIMITED`, `LLC`, `INC`, `CORP`, `PLC`, `GMBH`, `AG`, `SAS`, `SARL`, `BV`, `NV` 等。`NameEn` 应仅保留企业**核心品牌名或商号 (Trading Name)**,确保与官网首页主 Title 保持一致。若清洗后 `NameEn` 为空或仅剩通用词,该条目视为**无效命名并整体废弃**。
+
+# [QUOTA_SCHEDULING - 动态再分配算法]
+- **名额平移**:若 `CountryTasks` 指定的某国家因“官网失效/无法证伪”导致有效条目不足,允许实际输出数 < 理论配额。
+- **禁止凑数**:名额自动向质量更高、官网更活跃的国家转移,严禁为了填满 JSON 而降低审计标准。
+
+# [THOUGHT_PROCESS_LOGIC]
+1. 解析 `CountryTasks` 与 `BusinessConstraints` -> 2. 执行 {today} 实时检索 -> 3. **执行存在性三位一体核验(证伪熔断)** -> 4. **扫描官网内容(识别并过滤软404)** -> 5. **属地物理地址强校验** -> 6. **英文命名清洗** -> 7. **以官网为核心、AB管道为备份提取字段** -> 8. 生成纯净 JSON。
+
+# [STRICT_DATA_CONTRACT]
+属性命名遵循 PascalCase,禁止新增字段:
 [
-  {{{{
-    ""Region"":""string"",
-    ""Industry"":""string"",
-    ""Scale"":""string"",
-    ""NameCn"":""string"",
-    ""NameEn"":""string"",
-    ""Address"":""string"",
-    ""Scope"":""string"",
-    ""Contact"":""string"",
-    ""Phone"":""string"",
-    ""Email"":""string"",
-    ""SiteUrl"":""string"",
-    ""PostUrl"":[{{{{""Date"":""yyyy-MM-dd"",""Description"":""string"",""Url"":""string""}}}}],
-    ""RecLevel"":""Core|Backup"",
-    ""IntgAdvice"":""string""
-  }}}}
+  {{
+    ""Region"": ""string"",
+    ""Industry"": ""string"",
+    ""Scale"": ""string"",
+    ""NameCn"": ""string"",
+    ""NameEn"": ""string"",
+    ""Address"": ""string"",
+    ""Scope"": ""string"",
+    ""Contact"": ""string"",
+    ""Phone"": ""string"",
+    ""Email"": ""string"",
+    ""SiteUrl"": ""string"",
+    ""PostUrl"": [ {{ ""Date"": ""yyyy-MM-dd"", ""Description"": ""string"", ""Url"": ""string"" }} ],
+    ""RecLevel"": ""Core|Backup"",
+    ""IntgAdvice"": ""string""
+  }}
 ]
 
-# [OUTPUT_RULES]
-• 非空结果:仅输出 JSON
-• 空结果:仅输出 AuditReason 字符串
-• 不输出 Markdown / 解释 / 思考过程
-• JSON 首字符 [,末字符 ]
-• 空结果输出 []
+# [HUNYUAN_OUTPUT_GUARDRAILS]
+你是一个严格的 JSON 生成器。
+1. 最高禁令:绝对禁止编造任何不存在的企业、人名、链接或动态。
+2. 输出洁癖:不输出 Markdown 标记、不输出任何解释文字或思考过程。
+3. 边界控制:首字符 [,末字符 ]。结果为空输出 []。
 
-# [EXECUTE]
-立即执行:以 {DateTime.Now.Year} 为锚点,严格按 EXECUTION_PIPELINE 执行,任何步骤失败即终止并输出 AuditReason。
-";
-        }
+# [EXECUTION]
+立即执行:以动态年份为锚点,执行严苛的企业真实性审计与软 404 熔断逻辑,严禁虚构任何信息,按契约输出纯净 JSON。";
+}
 
         /// <summary>
         /// 失败审计结构

+ 6 - 1
OASystem/OASystem.Api/OAMethodLib/QiYeWeChatAPI/AppNotice/Library.cs

@@ -226,7 +226,12 @@ namespace OASystem.API.OAMethodLib.QiYeWeChatAPI.AppNotice
             }
             else if (ccp.CTable == 85) //机票预订
             {
-                Grp_AirTicketReservations _AirTicketReservations = _grpDeleRep.Query<Grp_AirTicketReservations>(s => s.Id == ccp.CId).First();
+                var _AirTicketReservations = _grpDeleRep.Query<Grp_AirTicketReservations>(s => s.Id == ccp.CId).First();
+                if (_AirTicketReservations == null)
+                {
+                    return false;
+                }
+            
                 info.CreateDt = _AirTicketReservations.CreateTime.ToString("yyyy-MM-dd HH:mm");
 
                 Sys_Users user = _grpDeleRep.Query<Sys_Users>(s => s.Id == _AirTicketReservations.CreateUserId).First();

+ 1 - 0
OASystem/OASystem.Api/OASystem.API.csproj

@@ -32,6 +32,7 @@
   <ItemGroup>
     <None Remove="Controllers\5x5tfaby.3rg~" />
     <None Remove="Controllers\GroupsController.cs~RF185e674.TMP" />
+    <None Remove="Controllers\GroupsController.cs~RF228e8da6.TMP" />
     <None Remove="Controllers\GroupsController.cs~RF265115.TMP" />
   </ItemGroup>
 

+ 28 - 0
OASystem/OASystem.Domain/Dtos/Groups/AirTicketResDto.cs

@@ -352,3 +352,31 @@ public class AirTicketFeeListDto : AirTicketResDto
 
 public class AirTicketFeeSaveDto : AirTicketFeeInfoView
 { }
+
+public class AirTicketCostTripUserDelDto : CurrUserDtoBase
+{
+    /// <summary>
+    /// 团组Id
+    /// </summary>
+    public int GroupId { get; set; }
+    
+    /// <summary>
+    /// 行程代码
+    /// </summary>
+    public string FlightsDescription { get; set; }
+
+    /// <summary>
+    /// 舱位类型
+    /// </summary>
+    public int CType { get; set; }
+
+    /// <summary>
+    /// 客户Id
+    /// </summary>
+    public int ClientId { get; set; }
+}
+
+public class CancelClientRefundDto : AirTicketCostTripUserDelDto
+{ 
+    
+}

+ 3 - 3
OASystem/OASystem.Domain/Entities/Groups/Grp_AirTicketReservations.cs

@@ -29,7 +29,7 @@ public class Grp_AirTicketReservations : EntityBase
     public int OriginalReservationId { get; set; }
 
     /// <summary>
-    /// 航段描述
+    /// 航段代码描述
     /// </summary>
     [SugarColumn(IsNullable = true, ColumnDataType = "varchar(500)")]
     public string FlightsDescription { get; set; }
@@ -37,7 +37,7 @@ public class Grp_AirTicketReservations : EntityBase
     /// <summary>
     /// 航班基础信息(去程、联程、返程)
     /// </summary>
-    [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(300)")]
+    [SugarColumn(IsNullable = true, IsJson = true, ColumnDataType = "varchar(500)")]
     public List<AirTicketBasicInfo> AirTicketBasicInfos { get; set; } = new List<AirTicketBasicInfo>();
 
     /// <summary>
@@ -476,7 +476,7 @@ public static class FlightParser
         if (string.IsNullOrWhiteSpace(rawText))
             return new List<FlightInfo>();
 
-        var lines = rawText.Trim().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
+        var lines = rawText.Trim().Split(new[] { '\n', '\r' ,','}, StringSplitOptions.RemoveEmptyEntries);
         var result = new List<FlightInfo>();
 
         foreach (var line in lines)

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

@@ -383,6 +383,7 @@ public class AirTicketFeeInfo
     /// <summary>
     /// 航班基础信息(去程、联程、返程)
     /// </summary>
+    [SugarColumn(IsJson = true)]
     public List<AirTicketBasicInfo> AirTicketBasicInfos { get; set; } = new List<AirTicketBasicInfo>();
 
     /// <summary>
@@ -398,6 +399,7 @@ public class AirTicketFeeInfo
     /// <summary>
     /// 费用信息(含退票信息)
     /// </summary>
+    [SugarColumn(IsJson = true)]
     public List<CustTicketInfo> CustTicketInfos { get; set; } = new List<CustTicketInfo>();
 
     /// <summary>

+ 448 - 59
OASystem/OASystem.Infrastructure/Repositories/Groups/AirTicketResRepository.cs

@@ -1129,6 +1129,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
             .Select((x, y, z, z1, u) => new AirTicketFeeListView()
             {
                 Id = x.Id,
+                No = x.RecordType,
                 CabinName = z.Name,
                 FlightDesc = x.FlightsDescription,
                 ClientNameList = x.ClientName,
@@ -1143,9 +1144,15 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
             .ToListAsync();
 
         // 4. 获取黑屏代码(三字码映射)
-        var threeCodes = await _sqlSugar.Queryable<Res_ThreeCode>()
+        var threeCodeList = await _sqlSugar.Queryable<Res_ThreeCode>()
             .Where(x => x.IsDel == 0)
-            .ToDictionaryAsync(x => x.Three, x => x.AirPort);
+            .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<Grp_GroupCostParameter>()
@@ -1179,6 +1186,13 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
         // 7. 处理航班描述和客户名称
         foreach (var item in airTicketFeeList)
         {
+            // 退票退票标识
+            if (item.No == 1)
+            {
+                item.CabinName += "(退票)"; 
+            }
+
+
             // 7.1 处理航班描述
             if (!string.IsNullOrWhiteSpace(item.FlightDesc))
             {
@@ -1195,12 +1209,13 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                         ? threeCodes[flightInfo.ArrivalAirport]?.ToString() ?? flightInfo.ArrivalAirport
                         : flightInfo.ArrivalAirport;
 
-                    string desc = $"{flightInfo.SequenceNo}.{departAirport}→{arrivalAirport}({flightInfo.DepartureDate})";
+                    string desc = $"{flightInfo.SequenceNo}.{departAirport}  {arrivalAirport}({flightInfo.DepartureDate})";
                     flightDescBuilder.AppendLine(desc);
                 }
 
                 item.FlightDesc = flightDescBuilder.ToString().TrimEnd();
             }
+            else item.FlightDesc = "录入数据不规范!请检查";
 
             // 7.2 处理客户名称
             if (!string.IsNullOrWhiteSpace(item.ClientNameList))
@@ -1440,9 +1455,6 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
             {
                 var first = g.First();
 
-                // 合并航班基础信息(去重)
-                var airTicketBasicInfos = g.SelectMany(x => x.AirTicketBasicInfos ?? new List<AirTicketBasicInfo>()).Distinct().ToList();
-
                 // 合并客户名称(去重)
                 var clientNames = string.Join(",", g.Select(x => x.ClientName).ToList());
                 var clientNameIds = ParseToIntListSafe(clientNames);
@@ -1465,29 +1477,33 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     auditsStr.Append($"{cabinTypeName}:{auditStatus};");
                 }
 
+                // 合并所有客户的机票信息
+                var custTicketInfos = g.SelectMany(x => x.CustTicketInfos ?? new List<CustTicketInfo>()).ToList();
+
+                // 累计退票金额
+                decimal refundAmount = custTicketInfos.Where(x => x.IsRefund).Sum(x => x.RefundRecord?.RefundAmount ?? 0m + x.RefundRecord?.NonRefundableTax ?? 0m);
+
+                // 付款金额
+                decimal payMoney = g.Sum(x => x.PayMoney) + refundAmount;
+
                 // 处理多舱位审核描述
                 var newFlight = new AirTicketFeeInfo
                 {
                     Id = first.Id,
                     FlightsDescription = g.Key,
-
-                    // 合并航班基础信息(去重)
-                    AirTicketBasicInfos = airTicketBasicInfos,
-
+                    AirTicketBasicInfos = g.FirstOrDefault()?.AirTicketBasicInfos ?? new List<AirTicketBasicInfo>(),
                     // 合并客户人员
                     ClientName = clientNames,
                     ClientNameIds = clientNameIds,
-
                     // 合并所有客户的机票信息
-                    CustTicketInfos = g.SelectMany(x => x.CustTicketInfos ?? new List<CustTicketInfo>()).ToList(),
-
+                    CustTicketInfos = custTicketInfos,
                     PriceDescription = first.PriceDescription,
 
                     // 付款信息
                     PayDId = first.PayDId,
                     ConsumptionPatterns = first.ConsumptionPatterns,
                     ConsumptionDate = first.ConsumptionDate,
-                    PayMoney = g.Sum(x => x.PayMoney),
+                    PayMoney = payMoney,
                     PaymentCurrency = first.PaymentCurrency,
                     CTDId = first.CTDId,
                     BankNo = first.BankNo,
@@ -1504,7 +1520,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     AuditGMDate = first.AuditGMDate,
                     AuditStr = auditsStr.ToString().Trim()
                 };
-
+                
                 return newFlight;
             })
             .ToList();
@@ -1519,28 +1535,6 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
         foreach (var item in groupedByFlights)
         {
             flightsDescAll.AppendLine(item.FlightsDescription);
-
-            // 添加测试数据
-            if (!item.AirTicketBasicInfos.Any())
-            {
-                item.AirTicketBasicInfos = FlightParser.ParseFlights(item.FlightsDescription)
-                    .Select(x => new AirTicketBasicInfo()
-                    {
-                        No = x.SequenceNo,
-                        FlightsCode = x.FlightNumber,
-                        FlightsCity = $"{x.DepartureAirport}/{x.ArrivalAirport}",
-                        FlightsDate = x.DepartureDate,
-                        FlightsTime = x.DepartureTime,
-                        ArrivedTime = x.ArrivalTime
-                    })
-                    .ToList();
-            }
-
-            // 添加测试费用数据(仅用于测试,生产环境应移除)
-            if (!item.CustTicketInfos.Any())
-            {
-                item.CustTicketInfos = GetTestCustomerData();
-            }
         }
         groupInfo.FlightsDescription = flightsDescAll.ToString();
 
@@ -1652,7 +1646,6 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
         // 1. 基础字段设置
         var currUserId = dto.CurrUserId;
         var groupId = dto.GroupAirInfo?.Id ?? 0;
-        var successMsg = new StringBuilder();
         var appPushBodyInfos = new List<AppPushBodyInfo>();
         var index = 1;
 
@@ -1677,13 +1670,14 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 // 设置公共字段
                 airFeeInfo.CreateUserId = currUserId;
                 airFeeInfo.IsDel = 0;
-                cardPaymentInfo.CreateUserId = currUserId;
                 cardPaymentInfo.DIId = groupId;
                 cardPaymentInfo.CTable = 85;
+                cardPaymentInfo.AuditGMDate = string.Empty;
                 cardPaymentInfo.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                 cardPaymentInfo.IsPay = cardPaymentInfo.PayDId == 72 ? 1 : 0;
                 cardPaymentInfo.PayThenMoney = cardPaymentInfo.PayMoney;
                 cardPaymentInfo.RMBPrice = cardPaymentInfo.PayMoney * cardPaymentInfo.DayRate;
+                cardPaymentInfo.CreateUserId = currUserId;
                 cardPaymentInfo.IsDel = 0;
 
                 // 判断是否存在(通过唯一键)
@@ -1708,7 +1702,6 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     }
                     airId = AirId;
                     cardId = CardId;
-
                     appPushBodyInfos.Add(new AppPushBodyInfo()
                     {
                         OperationType = 1,
@@ -1723,12 +1716,12 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                     var result = await UpdateExistingRecord(airFeeInfo, cardPaymentInfo, existingAir, groupId, index);
                     if (!result.Success)
                     {
-                        if (result.IsSkip)
-                        {
-                            successMsg.AppendLine(result.ErrorMessage);
-                            index++;
-                            continue;
-                        }
+                        //if (result.IsSkip)
+                        //{
+                        //    successMsg.AppendLine(result.ErrorMessage);
+                        //    index++;
+                        //    continue;
+                        //}
                         RollbackTran();
                         return JsonView<AppPushBodyView>.Fail(result.ErrorMessage);
                     }
@@ -1755,10 +1748,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 AppPushBodyInfos = appPushBodyInfos
             };
 
-            if (successMsg.Length > 0) successMsg.Append("其他新增成功!");
-            else successMsg.Append("保存成功!");
-
-            return JsonView<AppPushBodyView>.Success(successMsg.ToString(), view);
+            return JsonView<AppPushBodyView>.Success("保存成功", view);
         }
         catch (Exception ex)
         {
@@ -1775,9 +1765,9 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
     /// <param name="airTicketFeeInfos"></param>
     /// <param name="groupId"></param>
     /// <returns></returns>
-    private Task<List<AirTicketFeeInfo>> OrganizeAirTicketDataAsync(List<AirTicketFeeInfo> airTicketFeeInfos, int groupId)
+    private Task<List<AirTicketFeeOpInfo>> OrganizeAirTicketDataAsync(List<AirTicketFeeInfo> airTicketFeeInfos, int groupId)
     {
-        var result = new List<AirTicketFeeInfo>();
+        var result = new List<AirTicketFeeOpInfo>();
 
         foreach (var airInfo in airTicketFeeInfos)
         {
@@ -1794,17 +1784,21 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                 var customers = cabinGroup.ToList();
 
                 // 1. 正常记录(非退票客户)- 按舱位类型合并成一条数据
-                var normalCustomers = customers.Where(x => !x.IsRefund).ToList();
+                var normalCustomers = customers.ToList();
                 if (normalCustomers.Any())
                 {
                     var custRecord = _mapper.Map<AirTicketFeeOpInfo>(airInfo);
                     custRecord.ClientNum = normalCustomers.Count;
                     custRecord.ClientName = string.Join(",", normalCustomers.Select(x => x.ClientId));
                     custRecord.CustTicketInfos = normalCustomers;
-                    custRecord.Price = normalCustomers.Sum(x => x.ActualPrice);
+
+                    var totalTicketPrice = normalCustomers.Sum(x => x.ActualPrice + x.AdditionalServices?.Sum(x1 => x1.Amount) ?? 0.00m);
+                    custRecord.Price = totalTicketPrice;
                     custRecord.Currency = airInfo.PaymentCurrency;
-                    custRecord.PayMoney = normalCustomers.Sum(x => x.TotalTicketPrice);
+                    custRecord.PayMoney = totalTicketPrice;
                     custRecord.RecordType = 0; // 正常记录
+                    custRecord.DIId = groupId;
+                    custRecord.CType = normalCustomers.FirstOrDefault()?.CType ?? 0;
 
                     result.Add(custRecord);
                 }
@@ -1824,12 +1818,15 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
                         var refundAccount = refundGroup.Key;
 
                         var custRecord = _mapper.Map<AirTicketFeeOpInfo>(airInfo);
+                        custRecord.DIId = groupId;
                         custRecord.RecordType = 1; // 退票记录
+                        custRecord.CType = normalCustomers.FirstOrDefault()?.CType ?? 0;
                         custRecord.OriginalReservationId = custRecord.Id; // 关联原始记录
                         custRecord.ClientNum = refundCustomersInGroup.Count;
                         custRecord.ClientName = string.Join(",", refundCustomersInGroup.Select(x => x.ClientId));
                         custRecord.CustTicketInfos = refundCustomersInGroup;
 
+
                         // 计算退款金额(负数)
                         decimal totalRefundAmount = refundCustomersInGroup.Sum(x =>
                             (x.RefundRecord?.RefundAmount ?? 0) + (x.RefundRecord?.NonRefundableTax ?? 0));
@@ -1844,7 +1841,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
             }
         }
 
-        return Task.FromResult(result);
+        return Task.FromResult(result.OrderBy(x => x.RecordType).ToList());
     }
 
     /// <summary>
@@ -1939,7 +1936,7 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
 
         // 获取付款信息
         var existingCard = await _sqlSugar.Queryable<Grp_CreditCardPayment>()
-            .FirstAsync(x => x.CId == airId && x.DIId == groupId && x.CTable == 85 && x.IsDel == 0);
+            .FirstAsync(x => x.DIId == groupId && x.CTable == 85 && x.CId == airId &&  x.IsDel == 0);
 
         // 审核验证
         if (existingCard != null)
@@ -1967,11 +1964,12 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
         }
 
         int cardId = 0;
+        cardPaymentInfo.Id = existingCard?.Id ?? 0;
+        cardPaymentInfo.CId = airId;
 
         // 更新或新增付款信息
         if (existingCard != null)
         {
-            cardPaymentInfo.Id = existingCard.Id;
             var cardUpdateCount = await _sqlSugar.Updateable(cardPaymentInfo)
                 .IgnoreColumns(ignoreAllNullColumns: true)
                 .Where(x => x.Id == existingCard.Id)
@@ -1986,7 +1984,6 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
         }
         else
         {
-            cardPaymentInfo.CId = airId;
             var cardIdLong = await _sqlSugar.Insertable(cardPaymentInfo).ExecuteReturnIdentityAsync();
 
             if (cardIdLong < 1)
@@ -2002,6 +1999,398 @@ public class AirTicketResRepository : BaseRepository<Grp_AirTicketReservations,
 
     #endregion
 
+    /// <summary>
+    /// 行程内删除用户信息
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<JsonView> TripUserDeleteAsync(AirTicketCostTripUserDelDto dto)
+    {
+        // 基础数据查询(舱位类型)
+        var cabinDatas = await _sqlSugar
+            .Queryable<Sys_SetData>()
+            .Where(x => x.IsDel == 0 && x.STid == 44)
+            .Select(x => new { x.Id, x.Name })
+            .ToListAsync();
+
+        // 人员信息查询
+        var clientInfo = await _sqlSugar.Queryable<Crm_DeleClient>()
+            .Where(x => x.IsDel == 0 && x.Id == dto.ClientId)
+            .Select(x => new { x.Id, x.FirstName, x.LastName, x.Pinyin })
+            .FirstAsync();
+
+        // 客户名称
+        var clientName = clientInfo != null
+            ? $"{AesEncryptionHelper.Decrypt(clientInfo.FirstName)}{AesEncryptionHelper.Decrypt(clientInfo.LastName)}"
+            : $"ID:{dto.ClientId}";
+
+        BeginTran();
+        try
+        {
+            // 1. 查询对应的机票记录(按团组+航段+舱位+正常记录)
+            var airTicket = await _sqlSugar.Queryable<Grp_AirTicketReservations>()
+                .Where(x => x.DIId == dto.GroupId
+                    && x.FlightsDescription == dto.FlightsDescription
+                    && x.CType == dto.CType
+                    && x.RecordType == 0  // 正常记录
+                    && x.IsDel == 0)
+                .FirstAsync();
+
+            var cabinName = cabinDatas.FirstOrDefault(x => x.Id == dto.CType)?.Name ?? "未知舱位";
+
+            if (airTicket == null)
+            {
+                RollbackTran();
+                return JsonView.Fail($"未找到对应记录!(航段:{dto.FlightsDescription}, 舱位:{cabinName})");
+            }
+
+            // 1.1 检查人员是否在记录中
+            var hasClient = airTicket.CustTicketInfos?.Any(ct => ct.ClientId == dto.ClientId) == true;
+            if (!hasClient)
+            {
+                RollbackTran();
+                return JsonView.Fail($"人员【{clientName}(ID:{dto.ClientId})】不存在于该记录中!(航段:{dto.FlightsDescription}, 舱位:{cabinName})");
+            }
+
+            // 2. 查询对应的费用记录并验证状态
+            var cardPayment = await _sqlSugar.Queryable<Grp_CreditCardPayment>()
+                .Where(x => x.DIId == dto.GroupId && x.CId == airTicket.Id && x.CTable == 85 && x.IsDel == 0)
+                .FirstAsync();
+
+            if (cardPayment != null)
+            {
+                // 费用状态验证
+                if (cardPayment.IsAuditGM == 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail($"删除失败:当前费用已审核,不可删除人员!(航段:{dto.FlightsDescription}, 舱位:{cabinName}, 客户:{clientName})");
+                }
+                if (cardPayment.IsAuditGM == 3)
+                {
+                    RollbackTran();
+                    return JsonView.Fail($"删除失败:当前费用已自动审核,不可删除人员!(航段:{dto.FlightsDescription}, 舱位:{cabinName}, 客户:{clientName})");
+                }
+                if (cardPayment.IsPay == 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail($"删除失败:当前费用已付款,不可删除人员!(航段:{dto.FlightsDescription}, 舱位:{cabinName}, 客户:{clientName})");
+                }
+            }
+
+            // 3. 检查该人员是否有退票记录
+            var targetClientInfo = airTicket.CustTicketInfos?.FirstOrDefault(x => x.ClientId == dto.ClientId);
+            var hasRefund = targetClientInfo?.IsRefund == true;
+
+            if (hasRefund)
+            {
+                RollbackTran();
+                return JsonView.Fail($"删除失败:人员【{clientName}(ID:{dto.ClientId})】存在退票记录,不可删除!请先处理退票信息。");
+            }
+
+            // 4. 获取剩余人员列表
+            var remainingClients = airTicket.CustTicketInfos?
+                .Where(ct => ct.ClientId != dto.ClientId)
+                .ToList() ?? new List<CustTicketInfo>();  
+
+            // 5. 计算总金额(包含机票价格 + 附加服务费用)
+            decimal totalPrice = remainingClients.Sum(x =>
+                    (x?.ActualPrice ?? 0) + (x.AdditionalServices?.Sum(x1 => x1.Amount) ?? 0));
+
+            // 6. 更新或删除记录
+            if (remainingClients.Any())
+            {
+                // 6.1 更新机票记录
+                airTicket.Price = totalPrice;
+                airTicket.ClientNum = remainingClients.Count;
+                airTicket.ClientName = string.Join(",", remainingClients.Select(x => x.ClientId));
+                airTicket.CustTicketInfos = remainingClients;
+
+                var airUpdateCount = await _sqlSugar.Updateable(airTicket)
+                    .UpdateColumns(x => new { x.ClientNum, x.ClientName, x.Price, x.CustTicketInfos })
+                    .Where(x => x.Id == airTicket.Id)
+                    .ExecuteCommandAsync();
+
+                if (airUpdateCount < 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail("删除失败:更新机票记录失败!");
+                }
+
+                // 6.2 更新费用记录
+                if (cardPayment != null)
+                {
+                    cardPayment.PayMoney = totalPrice;
+                    cardPayment.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                    cardPayment.PayThenMoney = totalPrice;
+                    cardPayment.RMBPrice = totalPrice * (cardPayment?.DayRate ?? 1);
+
+                    var cardUpdateCount = await _sqlSugar.Updateable(cardPayment)
+                        .IgnoreColumns(ignoreAllNullColumns: true)
+                        .Where(x => x.Id == cardPayment.Id)
+                        .ExecuteCommandAsync();
+
+                    if (cardUpdateCount < 1)
+                    {
+                        RollbackTran();
+                        return JsonView.Fail("删除失败:更新费用记录失败!");
+                    }
+                }
+            }
+            else
+            {
+                // 7. 没有剩余人员,删除整条记录
+                var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+                // 7.1 软删除机票记录
+                airTicket.IsDel = 1;
+                airTicket.DeleteUserId = dto.CurrUserId;
+                airTicket.DeleteTime = now;
+
+                var airUpdateCount = await _sqlSugar.Updateable(airTicket)
+                    .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime })
+                    .Where(x => x.Id == airTicket.Id)
+                    .ExecuteCommandAsync();
+
+                if (airUpdateCount < 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail("删除失败:删除机票记录失败!");
+                }
+
+                // 7.2 软删除费用记录
+                if (cardPayment != null)
+                {
+                    cardPayment.IsDel = 1;
+                    cardPayment.DeleteUserId = dto.CurrUserId;
+                    cardPayment.DeleteTime = now;
+
+                    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();
+            return JsonView.Success($"删除成功!已从航段【{dto.FlightsDescription}】舱位【{cabinName}】中移除人员【{clientName}(ID:{dto.ClientId})】");
+        }
+        catch (Exception ex)
+        {
+            RollbackTran();
+            // 记录异常日志
+            // _logger.LogError(ex, "行程内删除用户信息失败,参数:{@dto}", dto);
+            return JsonView.Fail($"删除失败:{ex.Message}");
+        }
+    }
+
+    /// <summary>
+    /// 取消指定客户的退票
+    /// </summary>
+    /// <param name="dto">取消退票参数</param>
+    /// <returns></returns>
+    public async Task<JsonView> CancelClientRefundAsync(CancelClientRefundDto dto)
+    {
+        // 人员信息查询
+        var clientInfo = await _sqlSugar.Queryable<Crm_DeleClient>()
+            .Where(x => x.IsDel == 0 && x.Id == dto.ClientId)
+            .Select(x => new { x.Id, x.FirstName, x.LastName, x.Pinyin })
+            .FirstAsync();
+
+        // 客户名称
+        var clientName = clientInfo != null
+            ? $"{AesEncryptionHelper.Decrypt(clientInfo.FirstName)}{AesEncryptionHelper.Decrypt(clientInfo.LastName)}"
+            : $"ID:{dto.ClientId}";
+
+        BeginTran();
+        try
+        {
+            // 1. 查询退票记录
+            var refundRecord = await _sqlSugar.Queryable<Grp_AirTicketReservations>()
+                .Where(x => x.DIId == dto.GroupId
+                    && x.FlightsDescription == dto.FlightsDescription
+                    && x.CType == dto.CType
+                    && x.RecordType == 1  // 退票记录
+                    && x.IsDel == 0)
+                .FirstAsync();
+
+            if (refundRecord == null)
+            {
+                RollbackTran();
+                return JsonView.Fail("未找到退票记录!");
+            }
+
+            // 2. 查询退票费用记录并验证状态
+            var refundPayment = await _sqlSugar.Queryable<Grp_CreditCardPayment>()
+                .Where(x => x.DIId == dto.GroupId
+                    && x.CId == refundRecord.Id
+                    && x.CTable == 85
+                    && x.IsDel == 0)
+                .FirstAsync();
+
+            if (refundPayment != null)
+            {
+                if (refundPayment.IsAuditGM == 1 || refundPayment.IsAuditGM == 3)
+                {
+                    RollbackTran();
+                    return JsonView.Fail("取消失败:退票费用已审核,不可取消!");
+                }
+                if (refundPayment.IsPay == 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail("取消失败:退票费用已付款,不可取消!");
+                }
+            }
+
+            // 3. 解析退票人员列表
+            var refundClientIds = refundRecord.CustTicketInfos?
+                .Where(x => x.IsRefund)
+                .Select(x => x.ClientId)
+                .ToList() ?? new List<int>();
+
+            if (!refundClientIds.Contains(dto.ClientId))
+            {
+                RollbackTran();
+                return JsonView.Fail($"人员【{clientName}】不存在于该退票记录中!");
+            }
+
+            // 4. 查询关联的正常机票记录
+            var normalTicket = await _sqlSugar.Queryable<Grp_AirTicketReservations>()
+                .Where(x => x.Id == refundRecord.OriginalReservationId
+                    && x.RecordType == 0
+                    && x.IsDel == 0)
+                .FirstAsync();
+
+            if (normalTicket == null)
+            {
+                RollbackTran();
+                return JsonView.Fail("取消失败:关联的正常机票记录不存在!");
+            }
+
+            // 5. 更新正常机票中该客户的 IsRefund 标志
+            var normalTicketClients = normalTicket.CustTicketInfos ?? new List<CustTicketInfo>();
+            var targetClient = normalTicketClients.FirstOrDefault(x => x.ClientId == dto.ClientId);
+
+            if (targetClient != null)
+            {
+                targetClient.IsRefund = false;
+                targetClient.RefundRecord = null;  
+            }
+
+            // 6. 保存正常机票记录
+            normalTicket.CustTicketInfos = normalTicketClients;
+
+            var airNormalCount = await _sqlSugar.Updateable(normalTicket)
+                .UpdateColumns(x => new { x.CustTicketInfos })
+                .Where(x => x.Id == normalTicket.Id)
+                .ExecuteCommandAsync();
+
+            if (airNormalCount < 1)
+            {
+                RollbackTran();
+                return JsonView.Fail("取消失败:更新正常机票记录失败!");
+            }
+
+            // 7. 从退票记录中移除该客户(同时更新 CustTicketInfos)
+            var remainingRefundClients = refundRecord.CustTicketInfos?
+                .Where(x => x.ClientId != dto.ClientId)
+                .ToList() ?? new List<CustTicketInfo>();
+
+            if (remainingRefundClients.Any())
+            {
+                // 7.1 更新退票记录中的 CustTicketInfos
+                refundRecord.CustTicketInfos = remainingRefundClients;
+                refundRecord.ClientNum = remainingRefundClients.Count;
+                refundRecord.ClientName = string.Join(",", remainingRefundClients.Select(x => x.ClientId));
+
+                // 重新计算退款金额
+                decimal totalRefundAmount = remainingRefundClients.Sum(x =>
+                    (x.RefundRecord?.RefundAmount ?? 0) + (x.RefundRecord?.NonRefundableTax ?? 0));
+                refundRecord.Price = totalRefundAmount;
+
+                var refundRecordCount = await _sqlSugar.Updateable(refundRecord)
+                    .UpdateColumns(x => new { x.CustTicketInfos, x.ClientNum, x.ClientName, x.Price })
+                    .Where(x => x.Id == refundRecord.Id)
+                    .ExecuteCommandAsync();
+
+                if (refundRecordCount < 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail("取消退票失败:退票信息更新失败!");
+                }
+
+                // 7.2 更新退票费用记录
+                if (refundPayment != null)
+                {
+                    refundPayment.PayMoney = totalRefundAmount;
+                    refundPayment.RMBPrice = totalRefundAmount * (refundPayment?.DayRate ?? 1);
+                    refundPayment.UpdateDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+                    var refundPaymentCount = await _sqlSugar.Updateable(refundPayment)
+                        .UpdateColumns(x => new { x.PayMoney, x.RMBPrice, x.UpdateDate })
+                        .Where(x => x.Id == refundPayment.Id)
+                        .ExecuteCommandAsync();
+
+                    if (refundPaymentCount < 1)
+                    {
+                        RollbackTran();
+                        return JsonView.Fail("取消退票失败:退票费用信息更新失败!");
+                    }
+                }
+            }
+            else
+            {
+                // 8. 退票记录中没有客户了,删除整条退票记录
+                var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                refundRecord.IsDel = 1;
+                refundRecord.DeleteUserId = dto.CurrUserId;
+                refundRecord.DeleteTime = now;
+
+                var refundRecordCount = await _sqlSugar.Updateable(refundRecord)
+                    .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime })
+                    .Where(x => x.Id == refundRecord.Id)
+                    .ExecuteCommandAsync();
+
+                if (refundRecordCount < 1)
+                {
+                    RollbackTran();
+                    return JsonView.Fail("取消退票失败:删除退票记录失败!");
+                }
+
+                if (refundPayment != null)
+                {
+                    refundPayment.IsDel = 1;
+                    refundPayment.DeleteUserId = dto.CurrUserId;
+                    refundPayment.DeleteTime = now;
+
+                    var refundPaymentCount = await _sqlSugar.Updateable(refundPayment)
+                        .UpdateColumns(x => new { x.IsDel, x.DeleteUserId, x.DeleteTime })
+                        .Where(x => x.Id == refundPayment.Id)
+                        .ExecuteCommandAsync();
+
+                    if (refundPaymentCount < 1)
+                    {
+                        RollbackTran();
+                        return JsonView.Fail("取消退票失败:删除退票费用记录失败!");
+                    }
+                }
+            }
+
+            CommitTran();
+            return JsonView.Success($"取消退票成功!已将客户【{clientName}】的退票状态恢复正常");
+        }
+        catch (Exception ex)
+        {
+            RollbackTran();
+            return JsonView.Fail($"取消退票失败:{ex.Message}");
+        }
+    }
+
     /// <summary>
     /// 按ID删除机票记录 2026版
     /// 验证费用状态和退票记录