Forráskód Böngészése

其他款项付款方式增强&新增OP地接AI资料库模块

本次提交:
- 增强团组增减款项付款方式,支持预付款/尾款自动处理,费用计算与审核流程同步适配;
- 新增OP地接AI资料库功能,支持AI采集、资料管理、邮件生成、SSE流式推送等接口;
- 优化会务活动成本项下拉、自动审核与撤销审核逻辑;
- 增加通用decimal安全转换方法,完善注释与代码结构。
Lyyyi 2 hónapja%!(EXTRA string=óta)
szülő
commit
ea417161a4

+ 2 - 1
OASystem/EntitySync/Program.cs

@@ -185,6 +185,7 @@ db.CodeFirst.SetStringDefaultLength(50).BackupTable().InitTables(new Type[]
     //typeof(Grp_ConfProcessNode),// 会务流程节点     
     //typeof(Grp_ConfProcessLog),// 团组会务流程总览操作日志  
     //typeof(Res_InvitationAI),// 商邀公务AI  
-    typeof(Res_InvitationAI_NoGroup),// 商邀公务AI-无团组版  
+    //typeof(Res_InvitationAI_NoGroup),// 商邀公务AI-无团组版  
+    typeof(Res_OpLocalAI),// OP地接AI  
 });
 Console.WriteLine("数据库结构同步完成!");

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

@@ -4391,7 +4391,16 @@ namespace OASystem.API.Controllers
                             priName = $"{flightsCode}【{airPayType}】";
                             break;
                         case 98:  //  98 其他款项
-                            priName = $"[费用名称:{otherMoneyDatas.Find(it => payInfo.DIId == it.DiId && payInfo.CId == it.Id)?.PriceName ?? " -"}]";
+
+                            var info = otherMoneyDatas.Find(it => payInfo.DIId == it.DiId && payInfo.CId == it.Id);
+                            var name =  "-";
+                            if (info != null)
+                            {
+                                //name = $"{info.PriceName}({info.PaymentType} {info.PaymentPercent:#0}%)";
+                                name = $"{info.PriceName}({info.PaymentType})";
+                            }
+
+                            priName = $"[费用名称:{name}]";
                             break;
                         case 285:  //  285 收款退还
                             priName = $"[费用名称:{refundPaymentDatas.Find(it => payInfo.DIId == it.DiId && payInfo.CId == it.Id)?.PriceName ?? " -"}]";

+ 37 - 7
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -2726,7 +2726,6 @@ FROM
                 return Ok(jw);
             }
 
-
             #region 权限校验
 
             var allDownFile = new List<int>() { 21 };
@@ -13087,7 +13086,6 @@ FROM
             #endregion
 
             return Ok(await _decreasePaymentsRep.DecreasePaymentsSelect(dto));
-
         }
 
         /// <summary>
@@ -38245,8 +38243,6 @@ ORDER BY
             return Ok(JsonView(true, "SUCCESS!", new { Url = url, FileName = fileName }));
         }
 
-
-
         /// <summary>
         /// 会务成本保存 
         /// </summary>
@@ -38644,11 +38640,45 @@ ORDER BY
             return Ok(jw);
         }
 
+        /// <summary>
+        /// 获取指定团组的会务活动成本项名称列表(用于下拉提示)
+        /// </summary>
+        /// <param name="dto">查询参数,包含团组ID GroupId</param>
+        /// <returns>价格名称列表</returns>
         [HttpPost]
-        public async Task<IActionResult> QueryConferenceAffairsChild(QueryConferenceAffairsChildDto Dto)
+        public async Task<IActionResult> QueryConferenceAffairsChild([FromBody] QueryConferenceAffairsChildDto dto)
         {
-            return Ok(JsonView(true, "SUCCESS", _sqlSugar.Queryable<Grp_ConferenceAffairsCostChild>().Where(x => x.IsDel == 0 && x.Diid == Dto.GroupId)
-                .Select(x => x.PriceName).ToList()));
+            if (dto == null || dto.GroupId <= 0)
+            {
+                return Ok(JsonView(false, "团组ID无效", new List<string>()));
+            }
+
+            try
+            {
+                string paymentType = "预付款";
+                decimal paymentPercent = 80m;
+
+                var priceDatas = await _sqlSugar.Queryable<Grp_ConferenceAffairsCostChild>()
+                    .Where(x => x.IsDel == 0 && x.Diid == dto.GroupId)
+                    .Select(x => new {
+                        x.PriceName,
+                        PaymentType = paymentType,
+                        PaymentPercent = paymentPercent,
+                        Price = x.CostPrice,
+                        Quantity = 1,
+                        FeeTotal = x.CostPrice,
+                        ActualPaymentAmount = x.CostPrice * paymentPercent / 100m,
+                        x.Currency,
+                        x.Remark
+                    })               
+                    .ToListAsync();                 
+
+                return Ok(JsonView(true, "SUCCESS", priceDatas, priceDatas.Count()));
+            }
+            catch (Exception ex)
+            {
+                return Ok(JsonView(false, "查询失败,请稍后重试", new List<string>()));
+            }
         }
 
         #endregion

+ 6 - 14
OASystem/OASystem.Api/Controllers/PersonnelModuleController.cs

@@ -2085,6 +2085,9 @@ namespace OASystem.API.Controllers
                 Price = price,
                 Quantity = receiveInfo.Quantity,
                 FeeTotal = total,
+                PaymentType = "全款",
+                PaymentPercent = 100m,
+                ActualPaymentAmount = total,
                 Currency = 836,
                 FilePath = "",
                 OrbitalPrivateTransfer = 0,
@@ -2353,6 +2356,9 @@ namespace OASystem.API.Controllers
                 Price = price,
                 Quantity = quantity,
                 FeeTotal = total,
+                PaymentType = "全款", 
+                PaymentPercent = 100m,
+                ActualPaymentAmount = total,
                 Currency = 836,
                 FilePath = "",
                 OrbitalPrivateTransfer = 0,
@@ -2373,20 +2379,6 @@ namespace OASystem.API.Controllers
                 Remark = remark
             };
 
-
-            //1   2590 √
-            //2   2737 √ √
-            //3   2760 √ √
-            //4   2761 √ √
-            //5   2788 √ √
-            //6   2796 √ √
-            //7   2809 √ √
-            //8   2833 √ √
-            //9   2834 √ √
-            //10  2839 √ √
-            //11  2846 √ √
-
-
             //其他费用
             #region 调用方法
             JsonView groupData = await _otherPaymentRep.OpDecreasePayments(requestData);

+ 778 - 0
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -31,6 +31,7 @@ using System.Diagnostics;
 using System.Security.AccessControl;
 using TencentCloud.Common;
 using static NodaTime.TimeZones.TzdbZone1970Location;
+using static OASystem.API.Controllers.PersonnelModuleController;
 using static OASystem.API.OAMethodLib.GeneralMethod;
 using static OASystem.API.OAMethodLib.Hotmail.HotmailService;
 using static OASystem.API.OAMethodLib.JWTHelper;
@@ -8231,6 +8232,783 @@ WHERE
             return Ok(JsonView(result, result ? "删除成功" : "删除失败"));
         }
 
+        #endregion
+
+        #region OP地接资料 - AI
+
+        /// <summary>
+        /// OP地接资料AI 基础数据源
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceInit()
+        {
+            var itemNames = await OpLocalServiceAINames();
+            var countries = await InvitationAICountryName();
+            var orgScales = OrgScale.BuildInitialData().Select(x => x.Name).ToList();
+            var orgLevels = new List<string>() {
+                "全部",
+                "总部",
+                "分公司"
+            };
+
+            return Ok(JsonView(true, $"查询成功!", new
+            {
+                itemNames,
+                countries,
+                orgScales,
+                orgLevels
+            }));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 资料列表
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("{name}")]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAIItemByName(string name)
+        {
+            if (string.IsNullOrWhiteSpace(name)) return Ok(JsonView(false, "名称无效"));
+
+            // 1. 获取主表记录
+            var info = await _sqlSugar.Queryable<Res_OpLocalAI>()
+                .FirstAsync(x => x.IsDel == 0 && x.LocalName == name);
+
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(x => x.IsDel == 0 && x.TeamName.Equals(name));
+            string baseUrl = AppSettingsHelper.Get("OfficeBaseUrl")?.TrimEnd('/');
+
+            // 2. 分支处理:若无主表记录,尝试从团组表同步基本信息
+            if (info == null)
+            {
+                return Ok(JsonView(true, "暂无数据", new
+                {
+                    Id = 0,
+                    GroupId = groupInfo?.Id ?? 0,
+                    LocalName = name,
+                    AiCrawledDetails = new List<LocalAgencyInfo>(),
+                    Entry = new
+                    {
+                        Objective = groupInfo?.VisitPurpose ?? "商务会谈",
+                        OriginUnit = groupInfo?.ClientUnit ?? "",
+                        TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? "")
+                    }
+                }));
+            }
+
+            // 3. 数据填充(使用 ??= 语法确保 Null 安全)
+            info.EntryInfo ??= new LocalEntryInfo();
+
+            if (string.IsNullOrEmpty(info.EntryInfo.Objective))
+                info.EntryInfo.Objective = groupInfo?.VisitPurpose ?? "商务会谈";
+
+            if (string.IsNullOrEmpty(info.EntryInfo.OriginUnit))
+                info.EntryInfo.OriginUnit = groupInfo?.ClientUnit ?? "";
+
+            if (info.EntryInfo.TargetCountry?.Any() != true)
+                info.EntryInfo.TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? "");
+
+            // 4. 排序:ThenByDescending 确保多级排序生效
+            info.AiCrawledDetails = info.AiCrawledDetails
+                .OrderByDescending(x => x.IsChecked)
+                .ThenByDescending(x => x.OperatedAt)
+                .ToList();
+
+            var details = info?.AiCrawledDetails ?? new List<LocalAgencyInfo>();
+
+            if (details.Any())
+            {
+                foreach (var detail in details)
+                {
+                    // 附件路径处理
+                    if (detail.EmailInfo?.AttachmentPaths?.Any() == true)
+                    {
+                        detail.EmailInfo.AttachmentPaths = detail.EmailInfo.AttachmentPaths
+                            .Select(p => p.StartsWith("http") ? p : $"{baseUrl}/{p.TrimStart('/')}")
+                            .ToList();
+                    }
+                }
+            }
+
+            return Ok(JsonView(true, "查询成功!", new
+            {
+                info.Id,
+                info.GroupId,
+                info.LocalName,
+                AiCrawledDetails = details,
+                Entry = info.EntryInfo
+            }));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 设置词条
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAISetPrompt(OpLocalServiceAISetPromptDto dto)
+        {
+            // 基础校验
+            if (string.IsNullOrWhiteSpace(dto.LocalName) || dto.TargetCountry == null || dto.TargetCountry.Count == 0 || 
+                dto.ScaleTypes == null || dto.ScaleTypes.Count == 0)
+                return Ok(JsonView(false, "请传入有效的地接名称、国家、规模类型!"));
+
+            var localName = dto.LocalName;
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                .Where(x => x.IsDel == 0 && x.TeamName.Equals(localName))
+                .Select(x => new { x.Id, x.TeamName, x.VisitPurpose })
+                .FirstAsync();
+
+            int groupId = groupInfo?.Id ?? 0;
+
+            #region 数据库操作
+
+            // 数据库信息获取方式
+            var dataInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().Where(x => x.IsDel == 0 && x.LocalName.Equals(localName)).FirstAsync();
+
+            #region 词条信息
+
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+            var entryInfo = new LocalEntryInfo()
+            {
+                OriginUnit = dto.OriginUnit,
+                TargetCountry = dto.TargetCountry,
+                ScaleTypes = dto.ScaleTypes,
+                IsBackground = dto.IsBackground,
+                OrgLevel = dto.OrgLevel,
+                OtherConstraints = dto.OtherConstraints,
+                Operator = operatorName,
+                OperatedAt = DateTime.Now
+            };
+            #endregion
+
+            // 3.如果以上两种方式都没有查询到数据,则说明是新数据,需要添加到数据库
+            if (dataInfo == null)
+            {
+                // 3.1 新数据,需要添加到数据库
+                dataInfo = new Res_OpLocalAI()
+                {
+                    LocalName = localName,
+                    GroupId = groupId,
+                    EntryInfo = entryInfo,
+                    CreateUserId = dto.CurrUserId
+                };
+
+                var insert = await _sqlSugar.Insertable(dataInfo).ExecuteReturnIdentityAsync();
+                if (insert < 1)
+                {
+                    return Ok(JsonView(false, $"词条信息新增失败!"));
+                }
+                dataInfo.Id = insert;
+            }
+            else
+            {
+                // 3.2 数据存在 则更新数据(覆盖原有的AI爬取详情,保留原有的其他字段不变)
+                dataInfo.EntryInfo = entryInfo;
+
+                var update = await _sqlSugar.Updateable(dataInfo).ExecuteCommandAsync();
+                if (update < 1)
+                {
+                    return Ok(JsonView(false, $"词条信息更新失败!"));
+                }
+            }
+
+            #endregion
+
+            return Ok(JsonView(true, $"设置成功!", new
+            {
+                dataInfo.Id,
+                dataInfo.LocalName,
+                dataInfo.GroupId,
+                Entry = dataInfo.EntryInfo,
+            }));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 设置复选框选中 批量
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task<IActionResult> OpLocalServiceAISetChecked([FromBody] OpLocalServiceAISetCheckedDto dto)
+        {
+            // 1. 基础参数校验 (注意:这里移除了对 Guids.Any() 的强校验,允许空集合)
+            if (dto.Id < 1 || dto.CurrUserId < 1 || dto.Guids == null)
+                return Ok(JsonView(false, "请求参数不完整"));
+
+            // 2. 获取主记录
+            var invAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>()
+                .FirstAsync(x => x.IsDel == 0 && x.Id == dto.Id);
+
+            if (invAiInfo?.AiCrawledDetails == null || !invAiInfo.AiCrawledDetails.Any())
+                return Ok(JsonView(false, "数据不存在或集合为空"));
+
+            // 3. 准备更新所需数据
+            var guidSet = dto.Guids.ToHashSet();
+            var now = DateTime.Now;
+
+            // 获取操作人姓名
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.Id == dto.CurrUserId)
+                    .Select(x => x.CnName)
+                    .FirstAsync() ?? "-";
+
+            // 4. 单次遍历完成 重置 + 更新
+            // 如果 guidSet 为空,循环会将所有项的 IsChecked 置为 false
+            foreach (var item in invAiInfo.AiCrawledDetails)
+            {
+                if (guidSet.Contains(item.Guid))
+                {
+                    item.IsChecked = true;
+                }
+                else
+                {
+                    item.IsChecked = false;
+                }
+
+                item.OperatedAt = now;
+                item.Operator = operatorName;
+            }
+
+            // 5. 排序逻辑
+            // 即使是全量取消选中,也可以按最后操作时间排序,让最近变动的项在前
+            invAiInfo.AiCrawledDetails = invAiInfo.AiCrawledDetails
+                .OrderByDescending(x => x.OperatedAt)
+                .ToList();
+
+            // 6. 提交数据库
+            // 注意:SqlSugar 的 UpdateColumns 会序列化整个对象列表并覆盖数据库字段
+            await _sqlSugar.Updateable(invAiInfo)
+                .UpdateColumns(x => x.AiCrawledDetails)
+                .ExecuteCommandAsync();
+
+            return Ok(JsonView(true, guidSet.Any() ? "设置成功" : "已全部取消选中"));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 保存
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAISave(OpLocalServiceAISaveDto dto)
+        {
+            if (dto.Id < 1) return Ok(JsonView(false, "请先设置AI关键字"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (dto.AiCrawledDetail == null) return Ok(JsonView(false, "请选择保存的邀请方详情"));
+
+            var localAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>()
+            .Where(x => x.Id == dto.Id)
+            .FirstAsync();
+
+            if (localAiInfo == null) return Ok(JsonView(false, "数据信息为空"));
+
+            var dataList = localAiInfo?.AiCrawledDetails;
+
+            if (dataList == null) return Ok(JsonView(false, "邀请方数据信息为空"));
+
+            var datas = dataList.Where(x => x.Guid != dto.AiCrawledDetail.Guid).ToList();
+
+            var editInfo = dto.AiCrawledDetail;
+
+            // 如果 Guid 为空,说明是新增数据,需要生成新的 Guid
+            if (string.IsNullOrEmpty(editInfo.Guid))
+            {
+                editInfo.Guid = Guid.NewGuid().ToString("N");
+                editInfo.Source = 2; // 标记为手动新增数据
+                editInfo.OperatedAt = DateTime.Now;
+            }
+
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+            editInfo.Operator = opUserName;
+            editInfo.OperatedAt = DateTime.Now;
+
+            datas.Add(editInfo);
+            localAiInfo.AiCrawledDetails = datas.OrderByDescending(x => x.OperatedAt).ToList();
+
+            var editUpd = await _sqlSugar.Updateable(localAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+            if (editUpd < 1)
+            {
+                return Ok(JsonView(true, "保存失败"));
+            }
+
+            return Ok(JsonView(true, "保存成功"));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 单条删除
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAISingleDel(OpLocalServiceAISingleDelDto dto)
+        {
+            int id = dto.Id;
+            string guid = dto.Guid;
+            // 基础校验
+            if (id < 1 || string.IsNullOrWhiteSpace(guid))
+                return Ok(JsonView(false, "请传入有效的Id和Guid!"));
+
+            var dataInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().Where(x => x.IsDel == 0 && x.Id == id).FirstAsync();
+
+            if (dataInfo == null)
+                return Ok(JsonView(false, "当前数据信息不存在!"));
+
+            var newDataInfos = dataInfo.AiCrawledDetails.Where(x => x.Guid != guid).ToList();
+            dataInfo.AiCrawledDetails = newDataInfos.OrderByDescending(x => x.OperatedAt).ToList();
+
+            var update = await _sqlSugar.Updateable(dataInfo).ExecuteCommandAsync();
+            if (update < 1)
+            {
+                return Ok(JsonView(false, $"删除失败!"));
+            }
+
+            return Ok(JsonView(true, $"删除成功!", new
+            {
+                dataInfo.Id,
+                dataInfo.LocalName,
+                dataInfo.GroupId,
+                dataInfo.AiCrawledDetails
+            }));
+        }
+
+        /// <summary>
+        /// OP地接资料AI AI查询出的数据添加至商邀资料库
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAIInsertResource([FromBody] OpLocalServiceAIInsertResourceDto dto)
+        {
+            // 基础校验
+            if (dto.Id < 1 || dto.CurrUserId < 1 || dto.Guids == null || !dto.Guids.Any())
+                return Ok(JsonView(false, "参数不完整,请选择有效的单位数据"));
+
+            // 获取主记录
+            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+                .FirstAsync(x => x.IsDel == 0 && x.Id == dto.Id);
+
+            if (dataInfo?.AiCrawledDetails == null)
+                return Ok(JsonView(false, "当前数据信息不存在!"));
+
+            // 筛选出待转正的 AI 数据 (使用 HashSet 优化匹配)
+            var guidSet = dto.Guids.ToHashSet();
+            var targetAiDetails = dataInfo.AiCrawledDetails
+                .Where(x => x.Source != 0 && guidSet.Contains(x.Guid))
+                .ToList();
+
+            if (!targetAiDetails.Any())
+                return Ok(JsonView(false, "未找到符合条件的 AI 获取信息,无法重复添加或来源错误!"));
+
+            // 准备入库数据(批量构建)
+            var now = DateTime.Now;
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+            var rawLocalDatas = targetAiDetails.Select(item => {
+                // 修改内存中的状态为本地数据
+                item.Source = 0;
+                item.OperatedAt = now;
+                item.Operator = opUserName;
+
+                return new Res_InvitationOfficialActivityData()
+                {
+                    // 批量加密操作
+                    Country = AesEncryptionHelper.Encrypt(item.Region),
+                    UnitName = AesEncryptionHelper.Encrypt(item.NameCn),
+                    Address = AesEncryptionHelper.Encrypt(item.Address),
+                    Field = AesEncryptionHelper.Encrypt(item.Scope),
+                    Contact = AesEncryptionHelper.Encrypt(item.Contact),
+                    Tel = AesEncryptionHelper.Encrypt(item.Phone),
+                    Email = AesEncryptionHelper.Encrypt(item.Email),
+                    UnitWeb = AesEncryptionHelper.Encrypt(item.SiteUrl),
+                    LastUpdateUserId = dto.CurrUserId,
+                    LastUpdateTime = now,
+                    CreateUserId = dto.CurrUserId,
+                    CreateTime = now
+                };
+            }).ToList();
+
+            // 开启事务执行双表更新
+            var result = await _sqlSugar.UseTranAsync(async () =>
+            {
+                // 插入本地资料库
+                await _sqlSugar.Insertable(rawLocalDatas).ExecuteCommandAsync();
+
+                // 更新 AI 主表的状态(全量覆盖 JSON 列)
+                await _sqlSugar.Updateable(dataInfo)
+                    .UpdateColumns(x => x.AiCrawledDetails)
+                    .ExecuteCommandAsync();
+            });
+
+            return Ok(result.IsSuccess
+                ? JsonView(true, "成功添加至资料库并更新来源状态")
+                : JsonView(false, $"操作失败:{result.ErrorMessage}"));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 混元AI查询资料(SSE流式推送)
+        /// </summary>
+        [HttpPost]
+        public async Task OpLocalServiceAISearchStream([FromBody] OpLocalServiceAISearchDto dto)
+        {
+            HttpContext.InitializeSse();
+
+            try
+            {
+                await HttpContext.SendSseStepAsync(5, "正在加载配置...");
+
+                #region 1. 异步并行化验证 (Performance Boost)
+                var localAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().Where(x => x.IsDel == 0 && x.Id == dto.Id).FirstAsync();
+                var operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId)
+                    .Select(x => x.CnName).FirstAsync();
+
+                if (localAiInfo?.EntryInfo == null)
+                {
+                    await HttpContext.SendSseStepAsync(-1, "未找到有效的关键字配置。");
+                    return;
+                }
+
+                var entryInfo = localAiInfo.EntryInfo;
+                var targetCountrySet = new HashSet<string>(entryInfo.TargetCountry);
+                #endregion
+
+                await HttpContext.SendSseStepAsync(20, "正在解析本地典籍 (并行解密)...");
+
+                #region 2. 内存计算优化
+                //// 仅查询必要字段,减少 DataReader 压力
+                //var rawLocalDatas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
+                //    .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.UnitName))
+                //    .Select(x => new { x.Country, x.UnitName, x.Address, x.Field, x.Contact, x.Tel, x.Email })
+                //    .ToListAsync();
+
+                //// PLINQ 核心炼金:利用多核并行解密
+                //var allDecrypted = rawLocalDatas.AsParallel().Select(item => new InvitationAIInfo
+                //{
+                //    Guid = Guid.NewGuid().ToString("N"),
+                //    Source = 0, // 标识来源:本地
+                //    Region = AesEncryptionHelper.Decrypt(item.Country),
+                //    NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
+                //    Address = AesEncryptionHelper.Decrypt(item.Address),
+                //    Scope = AesEncryptionHelper.Decrypt(item.Field),
+                //    Contact = AesEncryptionHelper.Decrypt(item.Contact),
+                //    Phone = AesEncryptionHelper.Decrypt(item.Tel),
+                //    Email = AesEncryptionHelper.Decrypt(item.Email),
+                //    OperatedAt = DateTime.Now,
+                //    Operator = operatorName,
+                //}).ToList();
+
+                //// 筛选符合国家的本地数据
+                //var matchedCountries = allDecrypted.Where(x => targetCountrySet.Contains(x.Region)).ToList();
+                var matchedCountries = new List<LocalAgencyInfo>();
+
+                #endregion
+
+                #region 3. 混元 AI 协同炼金
+
+                // --- 差额补全 (Gap Filling) ---
+                var localDatas = new List<LocalAgencyInfo>();
+                var aiTasks = new List<CountryAIPormptInfo>();
+
+                if (targetCountrySet.Any())
+                {
+                    foreach (var item in targetCountrySet)
+                    {
+                        aiTasks.Add(new CountryAIPormptInfo() { 
+                            Count = entryInfo.NeedCount,
+                            Country = item
+                        });
+                    }
+                }
+
+                var hunyuanAIInvDatas = new List<LocalAgencyInfo>();
+                if (aiTasks.Any())
+                {  
+                    await HttpContext.SendSseStepAsync(60, $"AI 正在跨境检索 {string.Join(", ", aiTasks.Select(x => $"{x.Country}({x.Count}条)"))} 单位资料...");
+                    string searchQuestion = OpLocalBuildHunyuanPrompt(aiTasks, entryInfo);
+
+                    _logger.LogInformation(@"地接名称:{InvName};  混元AI查询提示词:{searchQuestion}", localAiInfo.LocalName, searchQuestion);
+                    string searchRaw = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(searchQuestion);
+
+                    var aiParsed = CleanAndParseJson<List<LocalAgencyInfo>>(searchRaw);
+                    if (aiParsed != null)
+                    {
+                        hunyuanAIInvDatas = aiParsed.Select(x => {
+                            x.Guid = Guid.NewGuid().ToString("N");
+                            x.Source = 1;
+                            x.Operator = operatorName;
+                            x.OperatedAt = DateTime.Now;
+                            return x;
+                        }).ToList();
+                    }
+                }
+                #endregion
+
+                await HttpContext.SendSseStepAsync(90, "正在同步成果至持久化仓库...");
+
+                #region 4. 数据融合与更新
+                var finalResult = localDatas.Concat(hunyuanAIInvDatas).ToList();
+
+                // 仅更新指定列,性能更优
+                localAiInfo.AiCrawledDetails = finalResult.OrderByDescending(x => x.OperatedAt).ToList();
+                bool isOk = await _sqlSugar.Updateable(localAiInfo)
+                    .UpdateColumns(x => x.AiCrawledDetails)
+                    .ExecuteCommandHasChangeAsync();
+
+                if (!isOk) await HttpContext.SendSseStepAsync(-1, $"数据持久化失败。");
+                #endregion
+
+                // 5. 终焉推送
+                await HttpContext.SendSseStepAsync(100, "操作成功!资料已全部就绪。", new
+                {
+                    localAiInfo.Id,
+                    AiCrawledDetails = finalResult.OrderByDescending(x => x.Source).ThenBy(x => x.Region).ToList()
+                });
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "SSE 管道熔断");
+                await HttpContext.SendSseStepAsync(-1, $"SSE 错误:{ex.InnerException.Message}({ex.Message})");
+            }
+            finally
+            {
+                await HttpContext.FinalizeSseAsync();
+            }
+        }
+
+        /// <summary>
+        ///  OP地接资料AI 混元AI续写(SSE流式推送)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task OpLocalServiceAICompleteTextStream([FromBody] OpLocalServiceAICompleteTextStreamDto dto)
+        {
+            HttpContext.InitializeSse();
+
+            try
+            {
+                // 初始化检查 (Progress: 5%)
+                var invAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().Where(x => x.Id == dto.Id).FirstAsync();
+                if (invAiInfo?.EntryInfo == null)
+                {
+                    await HttpContext.SendSseStepAsync(-1, "请先设置关键字信息!");
+                    return;
+                }
+                await HttpContext.SendSseStepAsync(10, "任务初始化完成,正在计算补齐缺口...");
+
+                // 任务拆解逻辑
+                var entryInfo = invAiInfo.EntryInfo;
+                var aiTasks = new List<CountryAIPormptInfo>();
+                int targetPerCountry = entryInfo.NeedCount;
+
+                foreach (var countryName in entryInfo.TargetCountry)
+                {
+                    var countryDataCount = invAiInfo.AiCrawledDetails.Count(x => x.Region == countryName);
+                    int aiNeedCount = targetPerCountry - countryDataCount;
+                    if (aiNeedCount > 0) aiTasks.Add(new() { Country = countryName, Count = aiNeedCount });
+                }
+
+                if (!aiTasks.Any())
+                {
+                    await HttpContext.SendSseStepAsync(100, "数据已满额,无需续写", invAiInfo.AiCrawledDetails);
+                    return;
+                }
+
+                // 准备 AI Prompt (Progress: 20%)
+                var existingNames = new HashSet<string>(invAiInfo.AiCrawledDetails.Where(x => x.Source == 1).Select(x => x.NameCn));
+                string promptOther = $"请基于以下已存在的名称列表进行推荐,避免重复:{string.Join(", ", existingNames)}。{entryInfo.OtherConstraints}";
+                entryInfo.OtherConstraints = promptOther; // 将去重提示注入 entryInfo,确保 Prompt 构建时包含该信息
+
+                // 构建 Question 
+                string question = OpLocalBuildHunyuanPrompt(aiTasks, entryInfo);
+
+                await HttpContext.SendSseStepAsync(30, "AI 正在深度检索跨境OP地接数据,请稍候...");
+
+                // 调用 AI (Progress: 30% - 80%)
+                string aiRawResponse = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(question);
+
+                await HttpContext.SendSseStepAsync(85, "数据已捕获,正在进行格式校验与去重...");
+
+                // 5. 解析与清洗数据
+                var hunyuanAIInvDatas = new List<LocalAgencyInfo>();
+                if (!string.IsNullOrWhiteSpace(aiRawResponse))
+                {
+                    string cleanJson = aiRawResponse.Trim();
+                    if (cleanJson.Contains("```json"))
+                    {
+                        cleanJson = Regex.Match(cleanJson, @"```json([\s\S]*?)```").Groups[1].Value.Trim();
+                    }
+                    else if (cleanJson.Contains("```"))
+                    {
+                        cleanJson = Regex.Match(cleanJson, @"```([\s\S]*?)```").Groups[1].Value.Trim();
+                    }
+                    hunyuanAIInvDatas = JsonConvert.DeserializeObject<List<LocalAgencyInfo>>(cleanJson) ?? new List<LocalAgencyInfo>();
+                }
+
+                string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+                foreach (var x in hunyuanAIInvDatas)
+                {
+                    x.Guid = Guid.NewGuid().ToString("N");
+                    x.Source = 1;
+                    x.Operator = operatorName;
+                    x.OperatedAt = DateTime.Now;
+                }
+
+                // 6. 数据库操作 (Progress: 95%)
+                invAiInfo.AiCrawledDetails.AddRange(hunyuanAIInvDatas);
+                invAiInfo.AiCrawledDetails = invAiInfo.AiCrawledDetails.OrderByDescending(x => x.OperatedAt).ToList();
+
+                var update = await _sqlSugar.Updateable(invAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+
+                if (update > 0)
+                {
+                    await HttpContext.SendSseStepAsync(100, $"AI 续写成功!新增 {hunyuanAIInvDatas.Count} 条数据", invAiInfo.AiCrawledDetails);
+                }
+                else
+                {
+                    await HttpContext.SendSseStepAsync(-1, "数据库更新失败");
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "SSE 续写异常");
+                await HttpContext.SendSseStepAsync(-1, $"炼金炸炉:{ex.Message}");
+            }
+            finally
+            {
+                await HttpContext.FinalizeSseAsync();
+            }
+        }
+
+        /// <summary>
+        /// OP地接资料AI 生成报价邮件(SSE流式推送)
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task OpLocalServiceAIGenerateEmailStream([FromBody] OpLocalServiceAIGenerateEmailStreamDto dto)
+        {
+            HttpContext.InitializeSse();
+            try
+            {
+                // 1. 基础校验与资源加载 (Progress: 5%)
+                if (dto.Id < 1 || dto.Guids == null || !dto.Guids.Any())
+                {
+                    await HttpContext.SendSseStepAsync(-1, "请求参数不完整,请选择单位");
+                    return;
+                }
+
+                var localAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().Where(x => x.Id == dto.Id).FirstAsync();
+                if (localAiInfo?.AiCrawledDetails == null)
+                {
+                    await HttpContext.SendSseStepAsync(-1, "基础信息不存在");
+                    return;
+                }
+
+                await HttpContext.SendSseStepAsync(10, "AI 正在分析考察团组背景与访问意图...");
+
+                // 2. 准备 AI 上下文数据 (Progress: 15%)
+                var clientInfoSources = localAiInfo.AiCrawledDetails.Where(x => dto.Guids.Contains(x.Guid)).ToList();
+
+                var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                    .Where(x => x.IsDel == 0 && x.Id == localAiInfo.GroupId)
+                    .Select(x => new { x.TeamName, x.VisitPNumber, x.VisitDate, x.VisitStartDate, x.VisitEndDate, x.VisitCountry })
+                    .FirstAsync();
+
+                string dateRange = $"{groupInfo.VisitStartDate:yyyy-MM-dd} ~ {groupInfo.VisitEndDate:yyyy-MM-dd}";
+
+                string operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+                // 3. 构建 Prompt 并调用 AI (Progress: 25%)
+                await HttpContext.SendSseStepAsync(30, $"AI 正在为 {clientInfoSources.Count} 家单位撰写定制化正式邮件...");
+
+                // 构建生成邮件的 Prompt
+                string pormpt = BuildBatchCreateEmailPrompt(
+                    new AICreateEmailTask() {
+                        PersonCount = groupInfo.VisitPNumber,
+                        DateRange = dateRange,
+                        Agencies = clientInfoSources,
+                        EntryInfo = localAiInfo.EntryInfo
+                    }
+                );
+
+                // 调用 AI (此处为阻塞式等待 AI 结果,若混元支持 Stream 可进一步拆解)
+                string aiResponse = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(pormpt);
+
+                await HttpContext.SendSseStepAsync(80, "邮件初稿已生成,正在进行 HTML 格式校验与转义处理...");
+
+                // 4. 解析结果 (Progress: 85%)
+                var hunyuanAIEmailDatas = new List<AICreateEmailInfo>();
+                if (!string.IsNullOrWhiteSpace(aiResponse))
+                {
+                    // 预处理:过滤 AI 可能返回的 Markdown 标记
+                    string cleanJson = aiResponse.Trim();
+                    if (cleanJson.StartsWith("```json"))
+                        cleanJson = cleanJson.Substring(7, cleanJson.Length - 10).Trim();
+                    else if (cleanJson.StartsWith("```"))
+                        cleanJson = cleanJson.Substring(3, cleanJson.Length - 6).Trim();
+
+                    try
+                    {
+                        // 解析并注入 Source 标识
+                        hunyuanAIEmailDatas = JsonConvert.DeserializeObject<List<AICreateEmailInfo>>(cleanJson);
+
+                    }
+                    catch (JsonException ex)
+                    {
+                        // 记录日志并考虑 fallback 策略
+                        _logger.LogError(ex, "Hunyuan AI 响应解析失败。原始数据:{Response}", aiResponse);
+                    }
+                }
+                if (hunyuanAIEmailDatas == null || !hunyuanAIEmailDatas.Any())
+                {
+                    await HttpContext.SendSseStepAsync(-1, "AI 格式解析失败,请尝试重新生成");
+                    return;
+                }
+
+                // 5. 更新数据模型 (Progress: 90%)
+                foreach (var client in clientInfoSources)
+                {
+                    var aiEmail = hunyuanAIEmailDatas.FirstOrDefault(x => x.Guid == client.Guid);
+                    if (aiEmail != null)
+                    {
+                        client.EmailInfo.Status = 2; // 已生成
+                        client.EmailInfo.EmailTitle = aiEmail.Subject;
+                        client.EmailInfo.EmailContent = aiEmail.Content;
+                        client.EmailInfo.Operator = operatorName;
+                        client.EmailInfo.OperatedAt = DateTime.Now;
+                    }
+                }
+                localAiInfo.AiCrawledDetails = clientInfoSources.OrderByDescending(x => x.IsChecked).ThenByDescending(x => x.OperatedAt).ToList();
+
+                // 6. 数据库持久化 (Progress: 95%)
+                // 采用增量更新策略,避免直接 Where 过滤掉其他未选中的数据
+                var update = await _sqlSugar.Updateable(localAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+                if (update > 0)
+                {
+                    await HttpContext.SendSseStepAsync(100, "邮件全部生成完毕并已存入团组资料库", localAiInfo.AiCrawledDetails);
+                }
+                else
+                {
+                    await HttpContext.SendSseStepAsync(-1, "数据库写入失败,请联系管理员");
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "邮件生成异常");
+                await HttpContext.SendSseStepAsync(-1, $"生成失败:{ex.Message}");
+            }
+            finally
+            {
+                await HttpContext.FinalizeSseAsync();
+            }
+        }
+
+        
+
         #endregion
     }
 }

+ 12 - 1
OASystem/OASystem.Api/Controllers/StatisticsController.cs

@@ -1306,7 +1306,18 @@ ORDER BY
 
                 #region 其他款项费用  98 isAuditSql
                 List<GroupDecreaseFeeView> groupDecreaseFeeViews = new List<GroupDecreaseFeeView>();
-                string groupDecreaseFeeSql = string.Format(@"Select dp.Id As DPId,dp.DiId As DPDiId,dp.PriceName,ccp.PayMoney,sd1.Name As PayMoneyCurrency,
+                string groupDecreaseFeeSql = string.Format(@"Select dp.Id As DPId,dp.DiId As DPDiId,
+                                                             CASE
+                                                             WHEN dp.PaymentPercent <> 100
+                                                                 THEN dp.PriceName
+                                                                      + '('
+                                                                      + dp.PaymentType
+                                                                      + ')'
+                                                                      -- + ' '
+                                                                      -- + CAST(CAST(dp.PaymentPercent AS INT) AS VARCHAR(10))
+                                                                      -- + '%)'
+                                                             ELSE dp.PriceName
+                                                             END AS PriceName,ccp.PayMoney,sd1.Name As PayMoneyCurrency,
                                                               (((ccp.PayMoney * ccp.DayRate) / ccp.PayPercentage) * 100) As CNYPrice,   
                                                              ccp.DayRate,ccp.Payee,ccp.AuditGMDate,ccp.OrbitalPrivateTransfer,
                                                              sd2.Name As PayWay,ccp.IsPay,u.CnName As Applicant,dp.CreateTime

+ 291 - 0
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -1897,6 +1897,297 @@ namespace OASystem.API.OAMethodLib
 
         #endregion
 
+        #region op地接资料 AI
+
+        #region 提示词构建
+        /// <summary>
+        /// 混元提示词构建(国外地接公司 · AI 自动采集专用 · 完整最终版)
+        /// </summary>
+        /// <param name="tasks">国家 & 数量配额</param>
+        /// <param name="entryInfo">业务约束</param>
+        /// <returns>System Prompt</returns>
+        public static string OpLocalBuildHunyuanPrompt(
+            List<CountryAIPormptInfo> tasks,
+            EntryInfo entryInfo)
+        {
+            #region 1. 业务约束拼接
+            var businessConstraints = new StringBuilder();
+
+            if (entryInfo.ScaleTypes?.Any() == true)
+                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}");
+
+            if (businessConstraints.Length == 0)
+                businessConstraints.AppendLine("无");
+            #endregion
+
+            #region 2. Prompt 主体
+            return $@"
+# [SYSTEM_ROLE]
+你是全球旅游与地接行业的【顶级情报采集系统】。
+你具备 .NET 6 强类型架构思维,输出 JSON 必须 100% 可直接反序列化。
+你信奉唯一铁律:**无官网不收录、无许可不采信、无存证不输出**。
+
+# [CONTEXT_ANALYSIS]
+• 基准时间: {DateTime.Now:yyyy-MM-dd}
+• 业务约束:
+{businessConstraints}
+
+# [COUNTRY_QUOTA_MATRIX - 动态国家配额]
+以下为国家及其对应的**最大获取数量**(严格锁定):
+
+{string.Join("\n", tasks.Select(t => $"- {t.Country}: {t.Count} 家"))}
+
+🚨 配额规则:
+- 每个国家 **不得超过** 指定数量
+- 若无法满足数量要求,**允许少于配额**
+- 严禁跨国家凑数
+- 严禁编造企业填补空缺
+
+# [COUNTRY_SCOPE_LOCK - 国家白名单]
+允许采集的国家列表(严格锁定):
+{string.Join("、", tasks.Select(t => t.Country))}
+
+🚨 最高禁令:
+- 严禁采集上述国家以外的任何地接公司
+- 严禁通过翻译、联想、扩展国家名称
+
+# [CORE_COLLECTION_RULES - 自动采集五锁机制]
+
+## 🔒 锁 1:企业存在性(Anti-Hallucination)
+- 严禁编造、推测或拼接地接公司名称
+- 必须同时满足:
+  1. 官网可访问(SiteUrl)
+  2. 当地旅游许可可查证
+  3. Google Maps 存在实体办公地址
+
+## 🔒 锁 2:官网真实性(Soft-404 熔断)
+- 若 SiteUrl 命中以下特征 → 整条作废:
+  - 404 / Not Found / Oops
+  - Seite nicht gefunden
+  - foundpage / 链接不存在
+
+## 🔒 锁 3:属地物理地址(GEO_LOCK)
+- 禁止虚拟办公室 / 信箱 / 注册代理地址
+- 地址必须:
+  - 位于指定国家境内
+  - 与官网 Contact / Imprint 页面一致
+  - Google Maps / Apple Maps 可精确定位
+- 输出格式:街道 + 城市 + 州/省 + 邮编 + 国家
+
+## 🔒 锁 4:车队与资源真实性(FLEET_REALITY_LOCK)
+- 必须同时包含:
+  - 数量(如:15辆)
+  - 品牌 + 车型(如:奔驰商务)
+  - 座位数(如:7座)
+- 推荐格式:
+  > 15辆奔驰商务(7座)
+- 模糊描述 → FleetInfo 返回空
+
+## 🔒 锁 5:Email 校验与复制规则(VALIDATE_THEN_COPY)
+
+### ✅ 校验流程
+AI 必须按顺序执行:
+1. 格式合法(xxx@domain.com)
+2. 域名可解析
+3. MX 记录存在(逻辑判断)
+4. 邮箱域名 ≈ 官网域名(关键)
+
+### ✅ 复制规则
+- **仅当 1+2+3+4 全部通过**
+  → 才写入 `Email`
+- 否则:
+  - `Email` 返回空
+  - 原始邮箱写入 `EmailInfo.Address`
+
+### 🚨 严禁
+- 将未通过校验的邮箱写入 Email
+- 为了“有数据”强行复制
+
+# [OUTPUT_SCHEMA - STRICT]
+属性命名 PascalCase,禁止新增字段:
+
+[
+  {{
+    ""Region"": ""string"",
+    ""Industry"": ""旅游与地接"",
+    ""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"",
+    ""RiskNote"": ""string"",
+    ""License"": {{
+      ""LicenseNo"": ""string"",
+      ""LocalLicense"": ""string"",
+      ""DotNumber"": ""string"",
+      ""AssociationMember"": ""string"",
+      ""LiabilityInsurance"": ""string""
+    }},
+    ""Resource"": {{
+      ""BusinessScope"": ""string"",
+      ""FleetInfo"": ""string"",
+      ""GuideInfo"": ""string"",
+      ""DirectContract"": ""string"",
+      ""IsDirectSupplier"": true
+    }}
+  }}
+]
+
+# [OUTPUT_GUARDRAILS]
+- 仅输出 JSON
+- 不输出 Markdown
+- 不输出解释、不输出思考过程
+- 首字符 `[`,末字符 `]`
+- 无结果时输出 `[]`
+
+# [EXECUTE_NOW]
+以 {DateTime.Now:yyyy-MM-dd} 为时间锚点,
+仅在以下国家范围内执行全自动采集:
+{string.Join("、", tasks.Select(t => t.Country))}
+并严格遵守各国配额限制,
+执行五锁机制,输出纯净 JSON。
+";
+            #endregion
+        }
+
+        public class AICreateEmailTask
+        {
+            public string Country { get; set; }
+            public string City { get; set; }
+            public int PersonCount { get; set; }
+            public string DateRange { get; set; }
+
+            public List<LocalAgencyInfo> Agencies { get; set; }
+            public EntryInfo EntryInfo { get; set; }
+        }
+
+        /// <summary>
+        /// 混元提示词构建(多公司 / 多国家 / 多城市)
+        /// </summary>
+        public static string BuildBatchCreateEmailPrompt(
+            AICreateEmailTask task)
+        {
+            var agenciesJson = JsonConvert.SerializeObject(
+                task.Agencies.Select(a => new
+                {
+                    a.NameCn,
+                    a.NameEn,
+                    a.Region,
+                    //a.City,
+                    a.Contact,
+                    a.Email,
+                    a.SiteUrl,
+                    a.Scope,
+                    a.Resource?.FleetInfo
+                }));
+
+            return $@"
+# [SYSTEM_ROLE]
+你是国际地接与商务考察的【用车与报价专家】。
+你具备 .NET 强类型架构思维,输出 JSON 必须 100% 可直接反序列化。
+
+# [CONTEXT]
+• 当前时间: {DateTime.Now:yyyy-MM-dd}
+• 发起单位: {task.EntryInfo.OriginUnit}
+• 出访人数: {task.PersonCount}
+• 出访日期: {task.DateRange}
+
+# [TASK]
+请针对以下每家地接公司,分别生成一封**首次报价请求邮件**:
+
+{agenciesJson}
+
+# [RULES - 每家公司独立处理]
+1. 每家公司:
+   - 国家 = Company.Region
+   - 城市 = Company.City
+   - 车型必须基于 Company.FleetInfo
+2. 禁止编造车型
+3. 若 FleetInfo 无法匹配人数:
+   - 请在邮件中说明
+   - 请对方推荐替代方案
+4. 邮件语言:
+   - 默认:英文
+   - Region = 日本 → 追加日文
+   - Region = 韩国 → 追加韩文
+
+# [OUTPUT_SCHEMA - STRICT]
+请返回 JSON 数组,每项对应一家公司:
+
+[
+  {{
+    ""Guid"": """",
+    ""NameCn"": ""string"",
+    ""Scope"": ""string"",
+    ""Subject"": ""string"",
+    ""Content"": ""string(纯文本,\n 表示换行)""
+  }}
+]
+
+# [OUTPUT_GUARDRAILS]
+- 仅输出 JSON
+- 不输出 Markdown
+- 不输出解释
+- 首字符 [,末字符 ]
+
+# [EXECUTE_NOW]
+基于上述公司列表,
+为每家公司生成一封高回复率的首次报价请求邮件,
+并以 AICreateEmailInfo 格式返回纯净 JSON。
+";
+        }
+
+        #endregion
+
+        #region 基础数据
+        /// <summary>
+        /// op地接资料AI 团组名称整理
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<List<InvitationAIInvNameView>> OpLocalServiceAINames()
+        {
+            // 预定义 UnionAll 查询
+            var query1 = _sqlSugar.Queryable<Grp_DelegationInfo>()
+                .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.TeamName))
+                .Select(x => new InvitationAIInvNameView { Id = x.Id, Name = x.TeamName, Source = 1, SortTime = x.VisitDate });
+
+            var unionQuery = _sqlSugar.UnionAll(query1);
+
+            // 先进行去重逻辑处理
+            var distinctQuery = _sqlSugar.Queryable(unionQuery)
+                    .PartitionBy(it => it.Name)
+                    .OrderByDescending(it => it.SortTime)
+                    .Select(it => it);
+
+            // 将去重后的结果包装,再进行全局
+            return await _sqlSugar.Queryable(distinctQuery)
+                    .OrderByDescending(it => it.Source)
+                    .OrderByDescending(it => it.SortTime)
+                    .ToListAsync();
+        }
+
+        #endregion
+
+        #endregion
+
         #endregion
 
         #region 团组汇率

+ 14 - 1
OASystem/OASystem.Domain/Dtos/Groups/DecreasePaymentsDto.cs

@@ -108,7 +108,6 @@ namespace OASystem.Domain.Dtos.Groups
         /// </summary>
         public string SupplierAddress { get; set; }
 
-
         /// <summary>
         /// 费用名称
         /// </summary>
@@ -128,6 +127,20 @@ namespace OASystem.Domain.Dtos.Groups
         /// </summary>
         public decimal FeeTotal { get; set; }
 
+        /// <summary>
+        /// 付款方式
+        /// </summary>
+        public string PaymentType { get; set; }
+        /// <summary>
+        /// 付款百分比
+        /// </summary>
+        public decimal PaymentPercent { get; set; }
+
+        /// <summary>
+        /// 实际付款金额(实际付款金额 = 费用总额 * 付款百分比)
+        /// </summary>
+        public decimal ActualPaymentAmount { get; set; }
+
         /// <summary>
         /// 费用币种
         /// </summary>

+ 101 - 0
OASystem/OASystem.Domain/Dtos/Resource/OpLocalServiceAIDto.cs

@@ -0,0 +1,101 @@
+using OASystem.Domain.Entities.Resource;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OASystem.Domain.Dtos.Resource
+{
+    public class OpLocalServiceAIDto
+    {
+    }
+
+    public class OpLocalServiceAISearchDto
+    {
+        public int Id { get; set; }
+
+        public int CurrUserId { get; set; }
+    }
+
+    public class OpLocalServiceAISetPromptDto
+    {
+        /// <summary>
+        /// 公务名称
+        /// 默认团组名称 团组名称不改变的情况下则用团组名称作为公务名称
+        /// </summary>
+        public string LocalName { get; set; }
+
+        public int GroupId { get; set; }
+
+        /// <summary>
+        /// 出访单位
+        /// </summary>
+        public string OriginUnit { get; set; }
+        /// <summary>
+        /// 出访国家
+        /// </summary>
+        public List<string> TargetCountry { get; set; }
+
+        /// <summary>
+        /// 规模类型
+        /// </summary>
+        public List<string> ScaleTypes { get; set; } = new List<string>();
+
+        /// <summary>
+        /// 是否需要华人单位背景
+        /// </summary>
+        public bool IsBackground { get; set; } = false;
+
+        /// <summary>
+        /// 公司层级
+        /// </summary>
+        public string OrgLevel { get; set; }
+
+        /// <summary>
+        /// 备注信息
+        /// </summary>
+        public string OtherConstraints { get; set; }
+
+        public int CurrUserId { get; set; }
+    }
+
+    public class OpLocalServiceAISetCheckedDto : OpLocalServiceAISearchDto
+    {
+        public List<string> Guids { get; set; }
+    }
+
+    public class OpLocalServiceAISaveDto : OpLocalServiceAISearchDto
+    {
+        public LocalAgencyInfo AiCrawledDetail { get; set; }
+    }
+
+    public class OpLocalServiceAISingleDelDto
+    {
+        public int Id { get; set; }
+
+        public string Guid { get; set; }
+
+    }
+
+    public class OpLocalServiceAIGenerateEmailDto : InvitationAISearchDto
+    {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public List<string> Guids { get; set; }
+    }
+
+    public class OpLocalServiceAIInsertResourceDto : OpLocalServiceAISetCheckedDto
+    { }
+
+    // OpLocalServiceAISearchStreamProgress
+
+    public class OpLocalServiceAICompleteTextStreamDto : OpLocalServiceAISearchDto
+    {
+    }
+
+    public class OpLocalServiceAIGenerateEmailStreamDto : OpLocalServiceAIGenerateEmailDto
+    {
+    }
+}

+ 151 - 156
OASystem/OASystem.Domain/Entities/Groups/ConferenceAffairsCost.cs

@@ -1,172 +1,167 @@
-namespace OASystem.Domain.Entities.Groups
+namespace OASystem.Domain.Entities.Groups;
+
+/// <summary>
+/// 会务活动主表
+/// </summary>
+public class Grp_ConferenceAffairsCost : Entities.EntityBase
+{
+    /// <summary>
+    /// 城市
+    /// </summary>
+    public string City { get; set; }
+
+    ///// <summary>
+    ///// 权限控制
+    ///// </summary>
+    //public string PermissionControl { get; set; }
+
+    /// <summary>
+    /// 团组Id
+    /// </summary>
+    public int Diid { get; set; }
+
+    /// <summary>
+    /// 分配金额
+    /// </summary>
+    public decimal AllocationAmount { get; set; }
+
+    /// <summary>
+    /// 分配币种
+    /// </summary>
+    public int AllocationCurrency { get; set; }
+
+    /// <summary>
+    /// 分配时汇率
+    /// </summary>
+    public decimal AllocationRate { get; set; }
+
+    /// <summary>
+    /// 分配团组Id
+    /// </summary>
+    public int AllocationGroupId { get; set; }
+
+    /// <summary>
+    /// 成本总计
+    /// </summary>
+    public decimal CostAll { get; set; }
+
+    /// <summary>
+    /// 报价总计
+    /// </summary>
+    public decimal BaoJiaAll { get; set; }
+
+    /// <summary>
+    /// 最终币种
+    /// </summary>
+    public int MostCurr { get; set; }
+}
+
+
+/// <summary>
+/// 会务活动子表
+/// </summary>
+public class Grp_ConferenceAffairsCostChild : Entities.EntityBase
 {
+    ///// <summary>
+    ///// 是否选中
+    ///// </summary>
+    //public bool isCheck { get; set; }
+
+    /// <summary>
+    /// 序号
+    /// </summary>
+    public int Index { get; set; }
+
+    /// <summary>
+    /// 费用类型
+    /// </summary>
+    public int PriceType { get; set; }
+
+    /// <summary>
+    /// 费用名称
+    /// </summary>
+    public string PriceName { get; set; }
+
+    /// <summary>
+    /// 成本费用
+    /// </summary>
+    public decimal CostPrice { get; set; }
+
     /// <summary>
-    /// 会务活动主表
-    /// </summary>
-    public class Grp_ConferenceAffairsCost : Entities.EntityBase
-    {
-        /// <summary>
-        /// 城市
-        /// </summary>
-        public string City { get; set; }
-
-        ///// <summary>
-        ///// 权限控制
-        ///// </summary>
-        //public string PermissionControl { get; set; }
-
-        /// <summary>
-        /// 团组Id
-        /// </summary>
-        public int Diid { get; set; }
-
-        /// <summary>
-        /// 分配金额
-        /// </summary>
-        public decimal AllocationAmount { get; set; }
-
-        /// <summary>
-        /// 分配币种
-        /// </summary>
-        public int AllocationCurrency { get; set; }
-
-        /// <summary>
-        /// 分配时汇率
-        /// </summary>
-        public decimal AllocationRate { get; set; }
-
-        /// <summary>
-        /// 分配团组Id
-        /// </summary>
-        public int AllocationGroupId { get; set; }
-
-        /// <summary>
-        /// 成本总计
-        /// </summary>
-        public decimal CostAll { get; set; }
-
-        /// <summary>
-        /// 报价总计
-        /// </summary>
-        public decimal BaoJiaAll { get; set; }
-
-        /// <summary>
-        /// 最终币种
-        /// </summary>
-        public int MostCurr { get; set; }
-    }
-
-
-    /// <summary>
-    /// 会务活动子表
-    /// </summary>
-    public class Grp_ConferenceAffairsCostChild : Entities.EntityBase
-    {
-        ///// <summary>
-        ///// 是否选中
-        ///// </summary>
-        //public bool isCheck { get; set; }
-
-        /// <summary>
-        /// 序号
-        /// </summary>
-        public int Index { get; set; }
-
-        /// <summary>
-        /// 费用类型
-        /// </summary>
-        public int PriceType { get; set; }
-
-        /// <summary>
-        /// 费用名称
-        /// </summary>
-        public string PriceName { get; set; }
-
-
-        /// <summary>
-        /// 成本费用
-        /// </summary>
-        public decimal CostPrice { get; set; }
-
-        /// <summary>
-        /// 币种
-        /// </summary>
-        public int Currency { get; set; }
-
-        /// <summary>
-        /// 当时汇率
-        /// </summary>
-        public decimal Rate { get; set; }
-
-        /// <summary>
-        /// 数量
-        /// </summary>
-        public int Count { get; set; }
-
-        /// <summary>
-        /// 单位
-        /// </summary>
-        public int Unit { get; set; }
-
-        /// <summary>
-        /// 系数
-        /// </summary>
-        public decimal Coefficient { get; set; }
-
-        /// <summary>
-        /// 报价金额
-        /// </summary>
-        public decimal BaoJiaPrice { get; set; }
-
-        /// <summary>
-        /// 附加值
-        /// </summary>
-        public decimal AddedValue { get; set; }
+    /// 币种
+    /// </summary>
+    public int Currency { get; set; }
 
+    /// <summary>
+    /// 当时汇率
+    /// </summary>
+    public decimal Rate { get; set; }
+
+    /// <summary>
+    /// 数量
+    /// </summary>
+    public int Count { get; set; }
+
+    /// <summary>
+    /// 单位
+    /// </summary>
+    public int Unit { get; set; }
+
+    /// <summary>
+    /// 系数
+    /// </summary>
+    public decimal Coefficient { get; set; }
 
-        /// <summary>
-        /// 明细
-        /// </summary>
-        public string Details { get; set; }
+    /// <summary>
+    /// 报价金额
+    /// </summary>
+    public decimal BaoJiaPrice { get; set; }
 
-        /// <summary>
-        /// 审核状态
-        /// </summary>
-        public int ReviewStatus { get; set; }
+    /// <summary>
+    /// 附加值
+    /// </summary>
+    public decimal AddedValue { get; set; }
 
-        /// <summary>
-        /// 团组Id
-        /// </summary>
-        public int Diid { get; set; }
+    /// <summary>
+    /// 明细
+    /// </summary>
+    public string Details { get; set; }
 
-        /// <summary>
-        /// 主表Id
-        /// </summary>
-        public int ConferenceAffairsCostId { get; set; }
+    /// <summary>
+    /// 审核状态
+    /// </summary>
+    public int ReviewStatus { get; set; }
 
-        /// <summary>
-        /// 图片路径
-        /// </summary>
-        public string Imgs { get; set; }  
-    }
+    /// <summary>
+    /// 团组Id
+    /// </summary>
+    public int Diid { get; set; }
 
+    /// <summary>
+    /// 主表Id
+    /// </summary>
+    public int ConferenceAffairsCostId { get; set; }
 
     /// <summary>
-    /// 会务活动权限控制
+    /// 图片路径
     /// </summary>
+    public string Imgs { get; set; }  
+}
 
-    public class Grp_ConferenceAffairsCostControl : Entities.EntityBase
-    {
-        /// <summary>
-        /// 团组Id
-        /// </summary>
-        public int Diid { get; set; }
-
-        /// <summary>
-        /// 用户Id
-        /// </summary>
-        public int UserId { get; set; }
-    }
 
+/// <summary>
+/// 会务活动权限控制
+/// </summary>
 
+public class Grp_ConferenceAffairsCostControl : Entities.EntityBase
+{
+    /// <summary>
+    /// 团组Id
+    /// </summary>
+    public int Diid { get; set; }
+
+    /// <summary>
+    /// 用户Id
+    /// </summary>
+    public int UserId { get; set; }
 }

+ 131 - 116
OASystem/OASystem.Domain/Entities/Groups/Grp_DecreasePayments.cs

@@ -1,119 +1,134 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace OASystem.Domain.Entities.Groups
+namespace OASystem.Domain.Entities.Groups;
+
+/// <summary>
+/// 团组增减款项表
+/// --其他款项
+/// </summary>
+[SugarTable("Grp_DecreasePayments")]
+public class Grp_DecreasePayments : EntityBase
 {
     /// <summary>
-    /// 团组增减款项表
-    /// --其他款项
-    /// </summary>
-    [SugarTable("Grp_DecreasePayments")]
-    public class Grp_DecreasePayments: EntityBase
-    {
-        /// <summary>
-        /// 团组外键编号
-        /// </summary>
-        [SugarColumn(IsNullable =true,ColumnDataType ="int")]
-        public int DiId { get; set; }
-
-        /// <summary>
-        /// 供应商地区
-        /// 1 国内(默认) 2 国外
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "int")]
-        public int SupplierArea { get; set; } = 1;
-
-        /// <summary>
-        /// 供应商类型
-        /// setdata 外键Id  StID = 21
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "int")]
-        public int SupplierTypeId { get; set; }
-
-        /// <summary>
-        /// 供应商名称
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(200)")]
-
-        public string SupplierName { get; set; }
-
-        /// <summary>
-        /// 供应商联系人
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(100)")]
-        public string SupplierContact { get; set; }
-
-        /// <summary>
-        /// 供应商联系电话
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
-        public string SupplierContactNumber { get; set; }
-
-        /// <summary>
-        /// 供应商社交账号
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(100)")]
-        public string SupplierSocialAccount { get; set; }
-
-        /// <summary>
-        /// 供应商邮箱
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(50)")]
-        public string SupplierEmail { get; set; }
-
-        /// <summary>
-        /// 供应商地址
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(300)")]
-        public string SupplierAddress { get; set; }
-
-        /// <summary>
-        /// 费用名称
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(225)")]
-        public string PriceName { get; set; }
-
-        /// <summary>
-        /// 单价
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
-        public decimal Price { get; set; }
-
-        /// <summary>
-        /// 数量
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
-        public decimal Quantity { get; set; }
-
-        /// <summary>
-        /// 费用总计
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
-        public decimal FeeTotal { get; set; }
-
-        /// <summary>
-        /// 费用币种
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "int")]
-        public int Currency { get; set; }
-        /// <summary>
-        /// 附件地址
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(120)")]
-        public string FilePath { get; set; }
-
-        /// <summary>
-        /// OTA订单号
-        /// </summary>
-        [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
-        public string OTAOrderNo { get; set; }
-
-        /// <summary>
-        /// 是否为选择的费用
-        /// </summary>
-        public int IsSelect { get; set; }
-    }
+    /// 团组外键编号
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "int")]
+    public int DiId { get; set; }
+
+    /// <summary>
+    /// 供应商地区
+    /// 1 国内(默认) 2 国外
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "int")]
+    public int SupplierArea { get; set; } = 1;
+
+    /// <summary>
+    /// 供应商类型
+    /// setdata 外键Id  StID = 21
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "int")]
+    public int SupplierTypeId { get; set; }
+
+    /// <summary>
+    /// 供应商名称
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(200)")]
+
+    public string SupplierName { get; set; }
+
+    /// <summary>
+    /// 供应商联系人
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(100)")]
+    public string SupplierContact { get; set; }
+
+    /// <summary>
+    /// 供应商联系电话
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "varchar(50)")]
+    public string SupplierContactNumber { get; set; }
+
+    /// <summary>
+    /// 供应商社交账号
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(100)")]
+    public string SupplierSocialAccount { get; set; }
+
+    /// <summary>
+    /// 供应商邮箱
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(50)")]
+    public string SupplierEmail { get; set; }
+
+    /// <summary>
+    /// 供应商地址
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "nvarchar(300)")]
+    public string SupplierAddress { get; set; }
+
+    #region 付款方式 2026-05-12
+
+    /// <summary>
+    /// 付款方式
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "varchar(225)")]
+    public string PaymentType { get; set; }
+
+    /// <summary>
+    /// 付款百分比
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "decimal(5,2)")]
+    public decimal PaymentPercent { get; set; }
+
+    /// <summary>
+    /// 实际付款金额(实际付款金额 = 费用总额 * 付款百分比)
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "decimal(18,2)")]
+    public decimal ActualPaymentAmount { get; set; }
+
+    #endregion
+
+    /// <summary>
+    /// 费用名称
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "varchar(225)")]
+    public string PriceName { get; set; }
+
+    /// <summary>
+    /// 单价
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
+    public decimal Price { get; set; }
+
+    /// <summary>
+    /// 数量
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "decimal(10,2)")]
+    public decimal Quantity { get; set; }
+
+    /// <summary>
+    /// 费用总计(应付金额)
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "decimal(18,2)")]
+    public decimal FeeTotal { get; set; }
+
+    /// <summary>
+    /// 费用币种
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "int")]
+    public int Currency { get; set; }
+    /// <summary>
+    /// 附件地址
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "varchar(120)")]
+    public string FilePath { get; set; }
+
+    /// <summary>
+    /// OTA订单号
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDataType = "varchar(200)")]
+    public string OTAOrderNo { get; set; }
+
+    /// <summary>
+    /// 是否为选择的费用
+    /// </summary>
+    public int IsSelect { get; set; }
 }

+ 220 - 0
OASystem/OASystem.Domain/Entities/Resource/Res_OpLocalAI.cs

@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OASystem.Domain.Entities.Resource
+{
+    /// <summary>
+    /// OP地接AI
+    /// </summary>
+    [SugarTable("Res_OpLocalAI", "OP地接AI")]
+    public  class Res_OpLocalAI : EntityBase
+    {
+        /// <summary>
+        /// 名称
+        /// </summary>
+        [SugarColumn(ColumnName = "LocalName", ColumnDescription = "名称", IsNullable = true, ColumnDataType = "varchar(150)")]
+        public string LocalName { get; set; }
+
+        /// <summary>
+        /// 团组ID
+        /// 注:自定义命名时,GroupID = 0;使用团组名称时,GroupID = Grp_DelegationInfo.Id
+        /// </summary>
+        [SugarColumn(ColumnName = "GroupId", ColumnDescription = "团组ID", IsNullable = true, ColumnDataType = "int")]
+        public int GroupId { get; set; }
+
+        /// <summary>
+        /// ai 抓取信息存储(含本地数据信息)
+        /// </summary>
+        [SugarColumn(ColumnName = "AiCrawledDetails", ColumnDescription = "ai 抓取信息存储(含本地数据信息)", IsJson = true, IsNullable = true, ColumnDataType = "varchar(max)")]
+        public List<LocalAgencyInfo> AiCrawledDetails { get; set; } = new List<LocalAgencyInfo>();
+
+        /// <summary>
+        /// 词条信息
+        /// </summary>
+        [SugarColumn(ColumnName = "Entries", ColumnDescription = "词条信息", IsJson = true, IsNullable = true, ColumnDataType = "varchar(max)")]
+        public LocalEntryInfo EntryInfo { get; set; } = new LocalEntryInfo();
+    }
+
+    /// <summary>
+    /// 单位信息
+    /// </summary>
+    public class LocalAgencyInfo
+    {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string Guid { get; set; }
+
+        /// <summary>
+        /// 复选框是否选中
+        /// </summary>
+        public bool IsChecked { get; set; } = false;
+
+        /// <summary>
+        /// 数据来源
+        /// 0-本地数据;1-第三方AI接口数据;2-用户手动输入数据
+        /// </summary>
+        public int Source { get; set; } = 0;
+
+        /// <summary>
+        /// 归属区域
+        /// </summary>
+        public string Region { get; set; }
+
+        /// <summary>
+        /// 名称(中文)
+        /// </summary>
+        public string NameCn { get; set; }
+        /// <summary>
+        /// 名称(英文)
+        /// </summary>
+        public string NameEn { get; set; }
+        /// <summary>
+        /// 地址
+        /// </summary>
+        public string Address { get; set; }
+        /// <summary>
+        /// 经营范围
+        /// </summary>
+        public string Scope { get; set; }
+        /// <summary>
+        /// 联系人
+        /// </summary>
+        public string Contact { get; set; }
+        /// <summary>
+        /// 联系电话
+        /// </summary>
+        public string Phone { get; set; }
+        /// <summary>
+        /// 联系邮箱
+        /// </summary>
+        public string Email { get; set; }
+        /// <summary>
+        /// 官网地址
+        /// </summary>
+        public string SiteUrl { get; set; }
+
+        /// <summary>
+        /// 推荐等级
+        /// 高/中/低
+        /// </summary>
+        public string RecLevel { get; set; }
+
+        /// <summary>
+        /// 对接建议
+        /// </summary>
+        public string IntgAdvice { get; set; }
+
+        /// <summary>
+        /// 邮件信息
+        /// </summary>
+        public LocalEmailInfo EmailInfo { get; set; } = new LocalEmailInfo();
+
+        /// <summary>
+        /// 合规与资质信息(JSON)
+        /// </summary>
+        public LocalAgencyLicense License { get; set; } = new LocalAgencyLicense();
+
+        /// <summary>
+        /// 资源与服务能力(JSON)
+        /// </summary>
+        public LocalAgencyResource Resource { get; set; } = new LocalAgencyResource();
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        public string Remark { get; set; }
+
+        /// <summary>
+        /// 操作时间
+        /// </summary>
+        public DateTime OperatedAt { get; set; }
+        /// <summary>
+        /// 操作人
+        /// </summary>
+        public string Operator { get; set; }
+    }
+
+    /// <summary>
+    /// 地接公司资质信息
+    /// </summary>
+    public class LocalAgencyLicense
+    {
+        /// <summary>
+        /// 营业执照 / 商业登记证编号
+        /// </summary>
+        public string LicenseNo { get; set; }
+
+        /// <summary>
+        /// 当地旅游行业许可
+        /// </summary>
+        public string LocalLicense { get; set; }
+
+        /// <summary>
+        /// 美国 DOT 编号
+        /// </summary>
+        public string DotNumber { get; set; }
+
+        /// <summary>
+        /// 是否 NTA / JATA / KATA 会员
+        /// </summary>
+        public string AssociationMember { get; set; }
+
+        /// <summary>
+        /// 旅行社责任险(有无 & 保额)
+        /// </summary>
+        public string LiabilityInsurance { get; set; }
+    }
+
+    /// <summary>
+    /// 地接公司资源与服务能力
+    /// </summary>
+    public class LocalAgencyResource
+    {
+        /// <summary>
+        /// 主营业务范围
+        /// </summary>
+        public string BusinessScope { get; set; }
+
+        /// <summary>
+        /// 车队 / 车辆类型
+        /// </summary>
+        public string FleetInfo { get; set; }
+
+        /// <summary>
+        /// 专职导游 / 司导
+        /// </summary>
+        public string GuideInfo { get; set; }
+
+        /// <summary>
+        /// 直签酒店 / 餐厅
+        /// </summary>
+        public string DirectContract { get; set; }
+
+        /// <summary>
+        /// 是否一手地接(是否转包)
+        /// </summary>
+        public bool IsDirectSupplier { get; set; }
+    }
+
+
+    /// <summary>
+    /// Op地接词条信息
+    /// </summary>
+    public class LocalEntryInfo:EntryInfo
+    {
+        
+    }
+
+    /// <summary>
+    /// op地接邮件信息
+    /// </summary>
+    public class LocalEmailInfo : EmailInfo
+    {
+       
+    }
+
+}

+ 28 - 0
OASystem/OASystem.Domain/ViewModels/Groups/DecreasePaymentsView.cs

@@ -24,7 +24,19 @@ namespace OASystem.Domain.ViewModels.Groups
         /// 费用金额
         /// </summary>
         public decimal FeeTotal { get; set; }
+        /// <summary>
+        /// 付款方式
+        /// </summary>
+        public string PaymentType { get; set; }
+        /// <summary>
+        /// 付款百分比
+        /// </summary>
+        public decimal PaymentPercent { get; set; }
 
+        /// <summary>
+        /// 实际付款金额(实际付款金额 = 费用总额 * 付款百分比)
+        /// </summary>
+        public decimal ActualPaymentAmount { get; set; }
         /// <summary>
         /// 费用币种
         /// </summary>
@@ -117,6 +129,22 @@ namespace OASystem.Domain.ViewModels.Groups
         /// </summary>
         public decimal feeTotal { get; set; }
 
+
+        /// <summary>
+        /// 付款方式
+        /// </summary>
+        public string PaymentType { get; set; }
+
+        /// <summary>
+        /// 付款百分比
+        /// </summary>
+        public decimal PaymentPercent { get; set; }
+
+        /// <summary>
+        /// 实际付款金额(实际付款金额 = 费用总额 * 付款百分比)
+        /// </summary>
+        public decimal ActualPaymentAmount { get; set; }
+
         /// <summary>
         /// 费用币种
         /// </summary>

+ 6 - 0
OASystem/OASystem.Domain/ViewModels/System/SetDataView.cs

@@ -47,6 +47,12 @@ namespace OASystem.Domain.ViewModels.System
     }
 
 
+    public class PaymentTypeView {
+
+        public string PaymentType { get; set; }
+        public decimal PaymentPercent { get; set; }
+    }
+
     public class EecSetDataInfoView 
     {
         public int ParentId { get; set; }

+ 496 - 234
OASystem/OASystem.Infrastructure/Repositories/Groups/DecreasePaymentsRepository.cs

@@ -15,6 +15,7 @@ using OASystem.Infrastructure.Repositories.System;
 using OASystem.Infrastructure.Tools;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Runtime.Intrinsics.Arm;
 using System.Text;
@@ -47,26 +48,85 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 sqlWhere += string.Format(@" And ccp.IsPay = {0} ", dto.IsPaySign);
             }
 
+            // 其他款项-查看团组全部费用UserIds
+            var permUserIds = new List<int>() {
+                400, // 李允
+            };
+
+            var typeIds = new List<int>() {
+                302, // 成都-会务活动
+                691, // 四川-会务活动
+                762, // 四川-赛事项目收入
+                1047,// 成都-赛事项目收入
+                1539,// 纽茵-会务活动
+                1548 // 重庆-会务活动
+            };
+
+            var isSpecialType = await _sqlSugar.Queryable<Grp_DelegationInfo>().AnyAsync(x => x.IsDel == 0 && x.Id == dto.DiId && typeIds.Contains(x.TeamDid));
+
+            if (!isSpecialType || !permUserIds.Contains(dto.UserId))
+            {
+                sqlWhere += string.Format(@" AND gdp.CreateUserId IN({0})", dto.UserId);
+            }
+
             //雷怡 2024-5-8 15:14 新增View字段 CreateTime
-            string sql = string.Format(@" Select gdp.Id,gdp.DiId,gdp.PriceName,gdp.FeeTotal,sd1.[Name] as Currency,gdp.FilePath,su.CnName as CreateUserName,
-                                              ccp.IsAuditGM as isAudit,ccp.IsPay,gdp.CreateTime
+            string sql = string.Format(@"Select gdp.Id,gdp.DiId,
+                                         CASE
+                                            WHEN gdp.PaymentPercent <> 100
+                                                THEN gdp.PriceName
+                                                     + '('
+                                                     + gdp.PaymentType
+                                                     + ')'
+                                                     -- + ' '
+                                                     -- + CAST(CAST(gdp.PaymentPercent AS INT) AS VARCHAR(10))
+                                                     -- + '%)'
+                                            ELSE gdp.PriceName
+                                            END AS PriceName,
+                                         -- gdp.FeeTotal, 
+                                         gdp.PaymentType,
+                                         gdp.PaymentPercent,
+                                         gdp.ActualPaymentAmount FeeTotal,
+                                         sd1.[Name] as Currency,gdp.FilePath,su.CnName as CreateUserName,ccp.IsAuditGM as isAudit,ccp.IsPay,gdp.CreateTime
                                               From Grp_DecreasePayments as gdp With(Nolock) Left Join Grp_CreditCardPayment as ccp With(Nolock) On gdp.Id = ccp.CId
                                               Left Join Sys_SetData as sd1 On gdp.Currency = sd1.Id
                                               Left Join Sys_Users as su On gdp.CreateUserId = su.Id
-                                              Where gdp.DiId = {0} And ccp.CTable = 98 {2} And ccp.IsDel = 0 And gdp.IsDel = 0 And gdp.CreateUserId in ({1}) ", dto.DiId, dto.UserId, sqlWhere);
-            List<DecreasePaymentsView> _DecreasePayments = await _sqlSugar.SqlQueryable<DecreasePaymentsView>(sql).ToListAsync();
+                                              Where gdp.DiId = {0} And ccp.CTable = 98 {1} And ccp.IsDel = 0 And gdp.IsDel = 0 ", dto.DiId, sqlWhere);
+            var _DecreasePayments = await _sqlSugar.SqlQueryable<DecreasePaymentsView>(sql).ToListAsync();
 
             return new JsonView() { Code = 200, Msg = MsgTips.Succeed, Data = _DecreasePayments };
         }
 
-        public async Task<JsonView> DecreasePaymentsSelect(DecreasePaymentsDto dto)
+        /// <summary>
+        /// 获取付款方式配置(STid = 138)
+        /// </summary>
+        public async Task<List<PaymentTypeView>> GetPaymentTypesAsync()
         {
+            const int PaymentSettingStId = 138;
+
+            var paymentTypes = await _sqlSugar
+                .Queryable<Sys_SetData>()
+                .Where(x => x.STid == PaymentSettingStId && x.IsDel == 0)
+                .ToListAsync();
+
+            return paymentTypes.Select(x =>
+            {
+                var y = new PaymentTypeView()
+                {
+                    PaymentType = x.Name,
+                    PaymentPercent = x.Remark.SafeParseDecimal(),
+                };
+
+                return y;
+            }).ToList();
+        }
 
+        public async Task<JsonView> DecreasePaymentsSelect(DecreasePaymentsDto dto)
+        {
             #region 团组下拉框
             var isGm = AppSettingsHelper
                         .Get<int>("DecreasePaymentsSelectIsGm")
                         .Contains(dto.UserId);
-            List<DecreasePaymentGroupView> _Delegations = new List<DecreasePaymentGroupView>();
+            var _Delegations = new List<DecreasePaymentGroupView>();
 
             if (isGm && dto.CTId != 81)
             {
@@ -75,7 +135,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
 
             if (!isGm)
             {
-                List<Grp_GroupsTaskAssignment> grp_GroupsTaskAssignment = Query<Grp_GroupsTaskAssignment>(a => a.IsDel == 0 && a.UId == dto.UserId && a.CTId == dto.CTId).ToList();
+                var grp_GroupsTaskAssignment = Query<Grp_GroupsTaskAssignment>(a => a.IsDel == 0 && a.UId == dto.UserId && a.CTId == dto.CTId).ToList();
                 string DiId = "";
                 if (grp_GroupsTaskAssignment.Count > 0)
                 {
@@ -136,13 +196,15 @@ namespace OASystem.Infrastructure.Repositories.Groups
 
             #region 其他下拉框查询
 
-            //支付方式
-            List<Sys_SetData> Payment = _sqlSugar.Queryable<Sys_SetData>().Where(a => a.STid == 14 && a.IsDel == 0).ToList();
-            List<SetDataInfoView> _Payment = _mapper.Map<List<SetDataInfoView>>(Payment);
+            var setdata = _sqlSugar.Queryable<Sys_SetData>().Where(a => a.IsDel == 0).ToList();
+
+            // 支付方式
+            var payment = setdata.Where(a => a.STid == 14).ToList();
+            var _payment = _mapper.Map<List<SetDataInfoView>>(payment);
 
-            //供应商 supplier
-            List<Sys_SetData> supplier = _sqlSugar.Queryable<Sys_SetData>().Where(a => a.STid == 21 && a.IsDel == 0).ToList();
-            List<SetDataInfoView> _supplier = _mapper.Map<List<SetDataInfoView>>(supplier);
+            // 供应商
+            var supplier = setdata.Where(a => a.STid == 21).ToList();
+            var _supplier = _mapper.Map<List<SetDataInfoView>>(supplier);
             if (_supplier.Count > 0)
             {
                 var d1 = _supplier.Find(it => it.Name.Equals("策划 公司"));
@@ -162,43 +224,63 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 }
 
             }
-            List<dynamic> _supplierArea = new List<dynamic>() {
+            var _supplierArea = new List<dynamic>() {
                 new { id=1,name = "国内" },
                 new { id=2,name = "国外" },
             };
 
+            // 付款方式
+            var _paymentOptions =  await GetPaymentTypesAsync();
+
             #endregion
 
             var data = new
             {
-                payment = _Payment,
+                payment = _payment,
                 groupName = _Delegations,
                 supplier = _supplier,
-                supplierArea = _supplierArea
+                supplierArea = _supplierArea,
+                paymentOptions = _paymentOptions,
             };
             return new JsonView() { Code = 200, Msg = MsgTips.Succeed, Data = data };
-
         }
 
         public async Task<JsonView> OpDecreasePayments(DecreasePaymentsOpDto dto)
         {
+            //  输入验证
+            if (dto == null) return new JsonView { Code = 400, Msg = "参数不能为空" };
+
             BeginTran();
             int id = 0;
             var grp_Decrease = _mapper.Map<Grp_DecreasePayments>(dto);
             var recheck = true;
 
-
             //处理费用总计
             if (grp_Decrease.FeeTotal == 0.00M)
             {
-                grp_Decrease.FeeTotal = grp_Decrease.Price * grp_Decrease.Price;
+                grp_Decrease.FeeTotal = grp_Decrease.Quantity * grp_Decrease.Price;
+            }
+
+            // 处理未选择付款类型
+            if (string.IsNullOrEmpty(dto.PaymentType))
+            {
+                grp_Decrease.PaymentType = "全款";
+                grp_Decrease.PaymentPercent = 100m;
             }
 
-            List<TeamRateDescAddCurrencyIdView> teamRates = await _teamRateRep.PostGroupTeamRateItemByDiIdAndCTableId(1, dto.DiId, 98);
+            // 处理此次 付款金额
+            grp_Decrease.ActualPaymentAmount = grp_Decrease.FeeTotal * grp_Decrease.PaymentPercent / 100m;
+
+            // 获取汇率(复用查询)
+            var teamRates = await _teamRateRep.PostGroupTeamRateItemByDiIdAndCTableId(1, dto.DiId, 98);
             var teamRate = teamRates.Where(it => it.CurrencyId == grp_Decrease.Currency).FirstOrDefault();
             if (teamRate == null)
             {
-                return new JsonView() { Code = 400, Msg = $"添加失败!团组汇率未设置{_sqlSugar.Queryable<Sys_SetData>().First(it => it.IsDel == 0 && it.Id == grp_Decrease.Currency)?.Name ?? ""}该币种汇率!" };
+                var currencyName = await _sqlSugar.Queryable<Sys_SetData>()
+                    .Where(it => it.IsDel == 0 && it.Id == grp_Decrease.Currency)
+                    .Select(it => it.Name)
+                    .FirstAsync() ?? "";
+                return new JsonView { Code = 400, Msg = $"添加失败!团组汇率未设置{currencyName}该币种汇率!" };
             }
 
             if (dto.Status == 1)//添加
@@ -208,67 +290,104 @@ namespace OASystem.Infrastructure.Repositories.Groups
 
                 #region C表赋值
 
-                Grp_CreditCardPayment C = new Grp_CreditCardPayment();
-                C.PayDId = dto.PayDId;
-                C.ConsumptionPatterns = "";
-                C.ConsumptionDate = "";
-                C.CTDId = 0;
-                C.BankNo = "";
-                C.CardholderName = "";
-                C.PayMoney = grp_Decrease.FeeTotal;
-                C.PaymentCurrency = grp_Decrease.Currency;
-
-                C.CompanyBankNo = "";
-                C.OtherBankName = dto.OtherBankName;
-                C.OtherSideNo = dto.OtherSideNo;
-                C.OtherSideName = dto.OtherSideName;
-                C.Remark = "";
-                C.CreateUserId = grp_Decrease.CreateUserId;
-                C.MFOperator = 0;
-                C.MFOperatorDate = "";
-                C.IsAuditDM = 0;
-                C.AuditDMOperate = 0;
-                C.AuditDMDate = "";
-                C.IsAuditMF = 0;
-                C.AuditMFOperate = 0;
-                C.AuditMFDate = "";
-                C.IsAuditGM = 0;
-                C.AuditGMOperate = 21;
-                C.AuditGMDate = "";
+                var ccp = new Grp_CreditCardPayment
+                {
+                    PayDId = dto.PayDId,
+                    ConsumptionPatterns = "",
+                    ConsumptionDate = "",
+                    CTDId = 0,
+                    BankNo = "",
+                    CardholderName = "",
+                    PayMoney = grp_Decrease.ActualPaymentAmount,
+                    PaymentCurrency = grp_Decrease.Currency,
+                    CompanyBankNo = "",
+                    OtherBankName = dto.OtherBankName,
+                    OtherSideNo = dto.OtherSideNo,
+                    OtherSideName = dto.OtherSideName,
+                    Remark = "",
+                    CreateUserId = grp_Decrease.CreateUserId,
+                    MFOperator = 0,
+                    MFOperatorDate = "",
+                    IsAuditDM = 0,
+                    AuditDMOperate = 0,
+                    AuditDMDate = "",
+                    IsAuditMF = 0,
+                    AuditMFOperate = 0,
+                    AuditMFDate = "",
+                    IsAuditGM = 0,
+                    AuditGMOperate = 21,
+                    AuditGMDate = "",
+                    DIId = grp_Decrease.DiId,
+                    CId = id,
+                    CTable = 98,
+                    PayPercentage = 100,
+                    PayThenMoney = 0,
+                    PayPercentageOld = 0,
+                    PayThenMoneyOld = 0,
+                    UpdateDate = "",
+                    Payee = dto.SupplierName,
+                    OrbitalPrivateTransfer = dto.OrbitalPrivateTransfer,
+                    ExceedBudget = 0,
+                };
 
                 //2025-04-07 第四次更改 PayDId == 72(刷卡) IsPay == 1
-                if (C.PayDId == 72) C.IsPay = 1;
-                else C.IsPay = 0;
-
-                C.DIId = grp_Decrease.DiId;
-                C.CId = id;
-                C.CTable = 98;
-                C.PayPercentage = 100;
-                C.PayThenMoney = 0;
-                C.PayPercentageOld = 0;
-                C.PayThenMoneyOld = 0;
-                C.UpdateDate = "";
-                C.Payee = dto.SupplierName;
-                C.OrbitalPrivateTransfer = dto.OrbitalPrivateTransfer;
-                C.ExceedBudget = 0;
-                //C.RMBPrice = 0.00f;
+                if (ccp.PayDId == 72) ccp.IsPay = 1;
+                else ccp.IsPay = 0;
 
                 if (teamRate.CurrencyCode.Equals("CNY"))
                 {
-                    C.DayRate = 1.0000M;
-                    C.RMBPrice = C.PayMoney;
+                    ccp.DayRate = 1.0000M;
+                    ccp.RMBPrice = ccp.PayMoney;
                 }
                 else
                 {
-                    C.DayRate = teamRate.Rate;
-                    C.RMBPrice = C.PayMoney * C.DayRate;
+                    ccp.DayRate = teamRate.Rate;
+                    ccp.RMBPrice = ccp.PayMoney * ccp.DayRate;
                 }
 
                 #endregion 
 
-                int cId = await _sqlSugar.Insertable(C).ExecuteReturnIdentityAsync();
+                int cId = await _sqlSugar.Insertable(ccp).ExecuteReturnIdentityAsync();
                 if (cId > 1)
                 {
+                    #region 付款类型为预付款时,添加尾款信息
+
+                    if (grp_Decrease.PaymentType.Contains("预付款"))
+                    {
+                        var balancePercent = 100m - grp_Decrease.PaymentPercent;
+                        grp_Decrease.PaymentType = "尾款";
+                        grp_Decrease.PaymentPercent = balancePercent;
+                        grp_Decrease.ActualPaymentAmount = grp_Decrease.FeeTotal * balancePercent / 100m;
+
+                        var balanceId = await AddAsyncReturnId(grp_Decrease);
+                        if (balanceId < 1)
+                        {
+                            RollbackTran();
+                            return new JsonView() { Code = 200, Msg = $"{grp_Decrease.PriceName}尾款信息添加失败!" };
+                        }
+
+                        ccp.CId = balanceId;
+                        ccp.PayMoney = grp_Decrease.ActualPaymentAmount;
+                        if (teamRate.CurrencyCode.Equals("CNY"))
+                        {
+                            ccp.DayRate = 1.0000M;
+                            ccp.RMBPrice = ccp.PayMoney;
+                        }
+                        else
+                        {
+                            ccp.DayRate = teamRate.Rate;
+                            ccp.RMBPrice = ccp.PayMoney * ccp.DayRate;
+                        }
+
+                        var balanceCId = await _sqlSugar.Insertable(ccp).ExecuteReturnIdentityAsync();
+                        if (balanceCId < 1)
+                        {
+                            RollbackTran();
+                            return new JsonView() { Code = 200, Msg = $"{grp_Decrease.PriceName}尾款信息添加失败!" };
+                        }
+                    }
+
+                    #endregion
 
                     #region 策划部内容同步添加至资料库
                     //var userInfo = _sqlSugar.Queryable<Sys_Users>()
@@ -382,86 +501,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     #endregion
 
                     #region 会务成本自动审核处理
-                    var hwids = _sqlSugar.Queryable<Sys_SetData>()
-                        .Where(x => x.IsDel == 0 && x.STid == 10 && x.Name.Contains("会务活动"))
-                        .Select(x => x.Id)
-                        .ToList();
-
-                    var groupTypeIsTrue = _sqlSugar.Queryable<Grp_DelegationInfo>()
-                        .Where(x => x.Id == dto.DiId && x.IsDel == 0)
-                        .Any(x => hwids.Contains(x.TeamDid));
 
-                    if (groupTypeIsTrue)
-                    {
-                        var main = _sqlSugar.Queryable<Grp_ConferenceAffairsCost>()
-                                            .First(x => x.Diid == dto.DiId && x.IsDel == 0);
-
-                        if (main != null)
-                        {
-                            var chiArr = _sqlSugar.Queryable<Grp_ConferenceAffairsCostChild>()
-                                .Where(x => x.Diid == dto.DiId && x.ConferenceAffairsCostId == main.Id && x.IsDel == 0)
-                                .ToList();
-                            var find = chiArr.FirstOrDefault(x => x.PriceName == dto.PriceName && x.ReviewStatus == 1);
-                            var isAuto = false;
-
-                            //存在已审的会务成本
-                            if (find != null)
-                            {
-
-                                //检索历史金额
-                                var histyPrice = _sqlSugar.Queryable<Grp_DecreasePayments>()
-                                    .LeftJoin<Grp_CreditCardPayment>((x, a) => a.IsDel == 0 && a.CTable == 98 && a.CId == x.Id)
-                                    .Where((x, a) => x.IsDel == 0 && x.DiId == dto.DiId && x.PriceName == dto.PriceName && x.Id != grp_Decrease.Id)
-                                    .Select((x, a) => new
-                                    {
-                                        x.FeeTotal,
-                                        x.Currency,
-                                        a.DayRate,
-                                        a.RMBPrice,
-                                        x.DiId,
-                                        x.PriceName,
-                                        x.Price,
-                                        x.Quantity,
-                                    })
-                                    .ToList();
-
-                                //币种不同情况
-                                if (true)
-                                {
-                                    //币种全转换为人民币
-                                    var rate = teamRates.Where(it => it.CurrencyId == grp_Decrease.Currency).FirstOrDefault();
-                                    if (rate != null)
-                                    {
-                                        grp_Decrease.FeeTotal *= rate.Rate;
-                                    }
-
-                                    var ConferenceAffairsCostFeeTotal = 0.00M;
-                                    ConferenceAffairsCostFeeTotal = find.Count * find.CostPrice * find.Rate;
-
-                                    var histyPriceValue = histyPrice.Sum(x => x.FeeTotal * x.DayRate);
-                                    if (ConferenceAffairsCostFeeTotal > histyPriceValue)
-                                    {
-                                        isAuto = ConferenceAffairsCostFeeTotal - histyPriceValue >= grp_Decrease.FeeTotal;
-                                    }
-                                }
-                            }
-
-                            if (isAuto)
-                            {
-
-                                var execCount = _sqlSugar.Updateable<Grp_CreditCardPayment>()
-                                      .SetColumns(a => new Grp_CreditCardPayment
-                                      {
-                                          IsAuditGM = 3, //自动审核通过
-                                          AuditGMOperate = 4,
-                                          AuditGMDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
-                                      })
-                                      .Where(a => a.CId == grp_Decrease.Id && a.CTable == 98 && a.DIId == grp_Decrease.DiId)
-                                      .ExecuteCommand();
-                                recheck = false;
-                            }
-                        }
-                    }
+                    var auditRes = await AutoAuditConferenceCostAsync(grp_Decrease, teamRates);
+                    recheck = auditRes.isRecheck;
 
                     #endregion
 
@@ -475,9 +517,42 @@ namespace OASystem.Infrastructure.Repositories.Groups
             }
             else if (dto.Status == 2)
             {
-                //已审核、已支付 不可修改
+                // 已审核、已支付 不可修改
                 var grp_CreditCardPayment = _sqlSugar.Queryable<Grp_CreditCardPayment>().First(a => a.CId == grp_Decrease.Id && a.CTable == 98 && a.CId == grp_Decrease.Id && a.IsDel == 0);
 
+                // 修改 付款类型验证 尾款时不能修改为非尾款
+                var editValid = _sqlSugar.Queryable<Grp_DecreasePayments>()
+                    .Where(a => a.Id == grp_Decrease.Id && a.IsDel == 0)
+                    .Select(a => new
+                    {
+                        a.PaymentType,
+                        a.PaymentPercent,
+                        a.Price,
+                        a.Quantity,
+                        a.PriceName,
+                        a.FeeTotal,
+                    })
+                    .First();
+                if (editValid.PaymentType.Contains("尾款"))
+                {
+                    // 原付款类型为尾款时,修改时必须包含尾款且付款比例、价格信息不变
+                    if (!dto.PaymentType.Contains("尾款"))
+                    {
+                        return new JsonView() { Code = 400, Msg = $"尾款信息不可修改为非尾款选项!" };
+                    }
+
+                    if (editValid.PriceName != dto.PriceName)
+                    {
+                        return new JsonView() { Code = 400, Msg = $"尾款信息的费用名称不可修改,若要修改请前往预付款修改!" };
+                    }
+
+                    //if (editValid.PriceName != dto.PriceName || editValid.Price != dto.Price || editValid.Quantity != dto.Quantity || 
+                    //    editValid.FeeTotal != dto.FeeTotal || editValid.PaymentPercent != dto.PaymentPercent)
+                    //{
+                    //    return new JsonView() { Code = 400, Msg = $"尾款信息的价格、数量、费用总计、付款比例不可修改,若要修改请前往预付款修改对应信息!" };
+                    //}
+                }
+
                 if (grp_CreditCardPayment == null) return new JsonView() { Code = 400, Msg = MsgTips.Fail };
 
                 var isAuditGM = grp_CreditCardPayment.IsAuditGM;
@@ -500,6 +575,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     Price = grp_Decrease.Price,
                     Quantity = grp_Decrease.Quantity,
                     FeeTotal = grp_Decrease.FeeTotal,
+                    PaymentType = grp_Decrease.PaymentType,
+                    PaymentPercent = grp_Decrease.PaymentPercent,
+                    ActualPaymentAmount = grp_Decrease.ActualPaymentAmount,
                     Currency = grp_Decrease.Currency,
                     FilePath = grp_Decrease.FilePath,
                     OTAOrderNo = grp_Decrease.OTAOrderNo,
@@ -512,12 +590,12 @@ namespace OASystem.Infrastructure.Repositories.Groups
                         if (teamRate.CurrencyCode.Equals("CNY"))
                         {
                             grp_CreditCardPayment.DayRate = 1.0000M;
-                            grp_CreditCardPayment.RMBPrice = grp_CreditCardPayment.PayMoney;
+                            grp_CreditCardPayment.RMBPrice = grp_Decrease.ActualPaymentAmount;
                         }
                         else
                         {
                             grp_CreditCardPayment.DayRate = teamRate.Rate;
-                            grp_CreditCardPayment.RMBPrice = grp_CreditCardPayment.PayMoney * grp_CreditCardPayment.DayRate;
+                            grp_CreditCardPayment.RMBPrice = grp_Decrease.ActualPaymentAmount * grp_CreditCardPayment.DayRate;
                         }
                     }
 
@@ -525,7 +603,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     int isPay = 0;
                     if (dto.PayDId == 72) isPay = 1;
 
-                    int CTable = await _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                    int cTable = await _sqlSugar.Updateable<Grp_CreditCardPayment>()
                         .Where(a => a.Id == grp_CreditCardPayment.Id)
                         .SetColumns(a => new Grp_CreditCardPayment
                         {
@@ -534,7 +612,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                             OtherBankName = dto.OtherBankName,
                             PayDId = dto.PayDId,
                             IsPay = isPay,
-                            PayMoney = grp_Decrease.FeeTotal,
+                            PayMoney = grp_Decrease.ActualPaymentAmount,
                             PaymentCurrency = grp_Decrease.Currency,
                             Payee = dto.SupplierName,
                             OrbitalPrivateTransfer = dto.OrbitalPrivateTransfer,
@@ -544,8 +622,164 @@ namespace OASystem.Infrastructure.Repositories.Groups
                         })
                         .ExecuteCommandAsync();
 
-                    if (CTable > 0)
+                    if (cTable > 0)
                     {
+                        #region 付款类型为预付款时,添加尾款信息
+
+                        if (grp_Decrease.PaymentType.Contains("预付款"))
+                        {
+                            // 检查尾款信息是否存在,存在则更新,不存在则添加
+                            var balanceInfo = await _sqlSugar.Queryable<Grp_DecreasePayments>()
+                                .Where(x => x.DiId == grp_Decrease.DiId && x.PriceName == grp_Decrease.PriceName && x.PaymentType.Contains("尾款") && x.IsDel == 0)
+                                .FirstAsync();
+
+                            var balancePercent = 100m - grp_Decrease.PaymentPercent;
+                            var paymentTypeName = "尾款";
+                            var paymentPercent = balancePercent;
+                            var actualPaymentAmount = grp_Decrease.FeeTotal * balancePercent / 100m;
+
+                            if (balanceInfo != null) // 更新尾款信息
+                            {
+                                bool balanceRes = await UpdateAsync(a => a.Id == balanceInfo.Id, a => new Grp_DecreasePayments
+                                {
+                                    DiId = grp_Decrease.DiId,
+                                    SupplierArea = grp_Decrease.SupplierArea,
+                                    SupplierTypeId = grp_Decrease.SupplierTypeId,
+                                    SupplierName = grp_Decrease.SupplierName,
+                                    SupplierContact = grp_Decrease.SupplierContact,
+                                    SupplierContactNumber = grp_Decrease.SupplierContactNumber,
+                                    SupplierSocialAccount = grp_Decrease.SupplierSocialAccount,
+                                    SupplierEmail = grp_Decrease.SupplierEmail,
+                                    SupplierAddress = grp_Decrease.SupplierAddress,
+                                    PriceName = grp_Decrease.PriceName,
+                                    Price = grp_Decrease.Price,
+                                    Quantity = grp_Decrease.Quantity,
+                                    FeeTotal = grp_Decrease.FeeTotal,
+                                    PaymentType = paymentTypeName,
+                                    PaymentPercent = paymentPercent,
+                                    ActualPaymentAmount = actualPaymentAmount,
+                                    Currency = grp_Decrease.Currency,
+                                    FilePath = grp_Decrease.FilePath,
+                                    OTAOrderNo = grp_Decrease.OTAOrderNo,
+                                    Remark = grp_Decrease.Remark,
+                                });
+
+                                if (!balanceRes)
+                                {
+                                    RollbackTran();
+                                    return new JsonView() { Code = 200, Msg = $"{grp_Decrease.PriceName}尾款信息更新失败!" };
+                                }
+
+                                var DayRate = 0.00m;
+                                var RMBPrice = 0.00m;
+                                if (teamRate != null)
+                                {
+                                    if (teamRate.CurrencyCode.Equals("CNY"))
+                                    {
+                                        DayRate = 1.0000M;
+                                        RMBPrice = actualPaymentAmount;
+                                    }
+                                    else
+                                    {
+                                        DayRate = teamRate.Rate;
+                                        RMBPrice = actualPaymentAmount * DayRate;
+                                    }
+                                }
+
+                                int balanceCTable = await _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                                    .Where(a => a.DIId == balanceInfo.DiId && a.CTable == 98 && a.CId == balanceInfo.Id)
+                                    .SetColumns(a => new Grp_CreditCardPayment
+                                    {
+
+                                        OtherSideName = dto.OtherSideName,
+                                        OtherSideNo = dto.OtherSideNo,
+                                        OtherBankName = dto.OtherBankName,
+                                        PayDId = dto.PayDId,
+                                        IsPay = isPay,
+                                        PayMoney = actualPaymentAmount,
+                                        PaymentCurrency = grp_Decrease.Currency,
+                                        Payee = dto.SupplierName,
+                                        OrbitalPrivateTransfer = dto.OrbitalPrivateTransfer,
+                                        DayRate = DayRate,
+                                        RMBPrice = RMBPrice,
+                                    })
+                                    .ExecuteCommandAsync();
+
+                                if (balanceCTable < 1)
+                                {
+                                    RollbackTran();
+                                    return new JsonView() { Code = 200, Msg = $"{grp_Decrease.PriceName}尾款信息更新失败!" };
+                                }
+
+                            }
+                            else // 添加尾款信息
+                            {
+                                grp_Decrease.PaymentType = paymentTypeName;
+                                grp_Decrease.PaymentPercent = paymentPercent;
+                                grp_Decrease.ActualPaymentAmount = actualPaymentAmount;
+
+                                var balanceId = await AddAsyncReturnId(grp_Decrease);
+                                if (balanceId < 1)
+                                {
+                                    RollbackTran();
+                                    return new JsonView() { Code = 200, Msg = $"{grp_Decrease.PriceName}尾款信息添加失败!" };
+                                }
+
+                                grp_CreditCardPayment.CId = balanceId;
+                                grp_CreditCardPayment.PayMoney = grp_Decrease.ActualPaymentAmount;
+                                if (teamRate.CurrencyCode.Equals("CNY"))
+                                {
+                                    grp_CreditCardPayment.DayRate = 1.0000M;
+                                    grp_CreditCardPayment.RMBPrice = grp_CreditCardPayment.PayMoney;
+                                }
+                                else
+                                {
+                                    grp_CreditCardPayment.DayRate = teamRate.Rate;
+                                    grp_CreditCardPayment.RMBPrice = grp_CreditCardPayment.PayMoney * grp_CreditCardPayment.DayRate;
+                                }
+
+                                var balanceCId = await _sqlSugar.Insertable(grp_CreditCardPayment).ExecuteReturnIdentityAsync();
+                                if (balanceCId < 1)
+                                {
+                                    RollbackTran();
+                                    return new JsonView() { Code = 200, Msg = $"{grp_Decrease.PriceName}尾款信息添加失败!" };
+                                }
+                            }
+                        }
+
+                        // 付款类型更改为全款时,删除同名称尾款信息
+                        if (grp_Decrease.PaymentType.Contains("全款"))
+                        {
+                            var balanceInfo = await _sqlSugar.Queryable<Grp_DecreasePayments>()
+                                .Where(x => x.DiId == grp_Decrease.DiId && x.PriceName == grp_Decrease.PriceName && x.PaymentType.Contains("尾款") && x.IsDel == 0)
+                                .FirstAsync();
+
+                            if (balanceInfo != null) {
+
+                                bool balanceRes = await UpdateAsync(a => a.Id == balanceInfo.Id,
+                                     a => new Grp_DecreasePayments
+                                     {
+                                         IsDel = 1,
+                                         DeleteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                                         DeleteUserId = dto.CreateUserId,
+                                         Remark = $"{balanceInfo.Remark};由预付款更改为全款时,自动删除:{grp_Decrease.PriceName}尾款信息"
+                                     });
+
+                                int balanceCTable = await _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                                   .Where(a => a.DIId == balanceInfo.DiId && a.CTable == 98 && a.CId == balanceInfo.Id)
+                                   .SetColumns(a => new Grp_CreditCardPayment
+                                   {
+                                       IsDel = 1,
+                                       DeleteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                                       DeleteUserId = dto.CreateUserId,
+                                       Remark = $"{balanceInfo.Remark};由预付款更改为全款时,自动删除:{grp_Decrease.PriceName}尾款信息"
+                                   })
+                                   .ExecuteCommandAsync();
+                            }
+                        }
+
+                        #endregion
+
                         #region 世运会自动审核处理
                         if (dto.DiId == 2590)
                         {
@@ -629,100 +863,126 @@ namespace OASystem.Infrastructure.Repositories.Groups
                         #endregion
 
                         #region 会务成本自动审核处理
-                        var hwids = _sqlSugar.Queryable<Sys_SetData>()
-                            .Where(x => x.IsDel == 0 && x.STid == 10 && x.Name.Contains("会务活动"))
-                            .Select(x => x.Id)
-                            .ToList();
 
-                        var groupTypeIsTrue = _sqlSugar.Queryable<Grp_DelegationInfo>()
-                            .Where(x => x.Id == dto.DiId && x.IsDel == 0)
-                            .Any(x => hwids.Contains(x.TeamDid));
+                        var auditRes = await AutoAuditConferenceCostAsync(grp_Decrease, teamRates);
+                        recheck = auditRes.isRecheck;
 
-                        if (groupTypeIsTrue)
-                        {
-                            var main = _sqlSugar.Queryable<Grp_ConferenceAffairsCost>()
-                                                .First(x => x.Diid == dto.DiId && x.IsDel == 0);
+                        #endregion
 
-                            if (main != null)
-                            {
-                                var chiArr = _sqlSugar.Queryable<Grp_ConferenceAffairsCostChild>()
-                                    .Where(x => x.Diid == dto.DiId && x.ConferenceAffairsCostId == main.Id && x.IsDel == 0)
-                                    .ToList();
-                                var find = chiArr.FirstOrDefault(x => x.PriceName == dto.PriceName && x.ReviewStatus == 1);
-                                var isAuto = false;
-
-                                //存在已审的会务成本
-                                if (find != null)
-                                {
+                        CommitTran();
 
-                                    //检索历史金额
-                                    var histyPrice = _sqlSugar.Queryable<Grp_DecreasePayments>()
-                                        .LeftJoin<Grp_CreditCardPayment>((x, a) => a.IsDel == 0 && a.CTable == 98 && a.CId == x.Id)
-                                        .Where((x, a) => x.IsDel == 0 && x.DiId == dto.DiId && x.PriceName == dto.PriceName && x.Id != grp_Decrease.Id)
-                                        .Select((x, a) => new
-                                        {
-                                            x.FeeTotal,
-                                            x.Currency,
-                                            a.DayRate,
-                                            a.RMBPrice,
-                                            x.DiId,
-                                            x.PriceName,
-                                            x.Price,
-                                            x.Quantity,
-                                        })
-                                        .ToList();
-
-                                    if (true)
-                                    {
-                                        //币种全转换为人民币
-                                        var rate = teamRates.Where(it => it.CurrencyId == grp_Decrease.Currency).FirstOrDefault();
-                                        if (rate != null)
-                                        {
-                                            grp_Decrease.FeeTotal *= rate.Rate;
-                                        }
+                        var data = new { ccpId = grp_CreditCardPayment.Id, sign = 2, dataId = grp_Decrease.Id, recheck };
 
-                                        var ConferenceAffairsCostFeeTotal = 0.00M;
-                                        ConferenceAffairsCostFeeTotal = find.Count * find.CostPrice * find.Rate;
+                        return new JsonView() { Code = 200, Msg = "修改成功!", Data = data };
+                    }
+                }
+            }
 
-                                        var histyPriceValue = histyPrice.Sum(x => x.FeeTotal * x.DayRate);
-                                        if (ConferenceAffairsCostFeeTotal > histyPriceValue)
-                                        {
-                                            isAuto = ConferenceAffairsCostFeeTotal - histyPriceValue >= grp_Decrease.FeeTotal;
-                                        }
+            RollbackTran();
+            return new JsonView() { Code = 400, Msg = MsgTips.Fail };
+        }
 
-                                    }
-                                }
+        /// <summary>
+        /// 会务成本自动审核
+        /// </summary>
+        /// <param name="paymentInfo"></param>
+        /// <param name="teamRates"></param>
+        /// <returns></returns>
+        private async Task<(bool isAudit, bool isRecheck)> AutoAuditConferenceCostAsync(Grp_DecreasePayments paymentInfo, List<TeamRateDescAddCurrencyIdView> teamRates)
+        {
+            var hwids = _sqlSugar.Queryable<Sys_SetData>()
+                           .Where(x => x.IsDel == 0 && x.STid == 10 && x.Name.Contains("会务活动"))
+                           .Select(x => x.Id)
+                           .ToList();
 
-                                if (isAuto)
-                                {
+            var groupTypeIsTrue = _sqlSugar.Queryable<Grp_DelegationInfo>()
+                .Where(x => x.Id == paymentInfo.DiId && x.IsDel == 0)
+                .Any(x => hwids.Contains(x.TeamDid));
 
-                                    var execCount = _sqlSugar.Updateable<Grp_CreditCardPayment>()
-                                    .SetColumns(a => new Grp_CreditCardPayment
-                                    {
-                                        IsAuditGM = 3, //自动审核通过
-                                        AuditGMOperate = 4,
-                                        AuditGMDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
-                                    })
-                                    .Where(a => a.CId == grp_Decrease.Id && a.CTable == 98 && a.DIId == grp_Decrease.DiId)
-                                    .ExecuteCommand();
-                                    recheck = false;
-                                }
-                            }
-                        }
+            if (groupTypeIsTrue)
+            {
+                var main = _sqlSugar.Queryable<Grp_ConferenceAffairsCost>().First(x => x.Diid == paymentInfo.DiId && x.IsDel == 0);
 
-                        #endregion
+                if (main != null)
+                {
+                    var chiArr = _sqlSugar.Queryable<Grp_ConferenceAffairsCostChild>()
+                        .Where(x => x.Diid == paymentInfo.DiId && x.ConferenceAffairsCostId == main.Id && x.IsDel == 0)
+                        .ToList();
+                    var find = chiArr.FirstOrDefault(x => x.PriceName == paymentInfo.PriceName && x.ReviewStatus == 1);
+                    var isAuto = false;
 
-                        CommitTran();
+                    //存在已审的会务成本
+                    if (find != null)
+                    {
+                        //检索历史金额
+                        var histyPrice = _sqlSugar.Queryable<Grp_DecreasePayments>()
+                            .LeftJoin<Grp_CreditCardPayment>((x, a) => a.IsDel == 0 && a.CTable == 98 && a.CId == x.Id)
+                            .Where((x, a) => x.IsDel == 0 && x.DiId == paymentInfo.DiId && x.PriceName == paymentInfo.PriceName && 
+                            !x.PaymentType.Contains("尾款") && x.Id != paymentInfo.Id)
+                            .Select((x, a) => new
+                            {
+                                x.FeeTotal,
+                                x.Currency,
+                                a.DayRate,
+                                a.RMBPrice,
+                                x.DiId,
+                                x.PriceName,
+                                x.Price,
+                                x.Quantity,
+                            })
+                            .ToList();
 
-                        var data = new { ccpId = grp_CreditCardPayment.Id, sign = 2, dataId = grp_Decrease.Id, recheck };
+                        //币种全转换为人民币
+                        var rate = teamRates.Where(it => it.CurrencyId == paymentInfo.Currency).FirstOrDefault();
+                        if (rate != null)
+                        {
+                            paymentInfo.FeeTotal *= rate.Rate;
+                        }
 
-                        return new JsonView() { Code = 200, Msg = "修改成功!", Data = data };
+                        var ConferenceAffairsCostFeeTotal = 0.00M;
+                        ConferenceAffairsCostFeeTotal = find.Count * find.CostPrice * find.Rate;
+
+                        var histyPriceValue = histyPrice.Sum(x => x.FeeTotal * x.DayRate);
+                        if (ConferenceAffairsCostFeeTotal > histyPriceValue)
+                        {
+                            isAuto = ConferenceAffairsCostFeeTotal - histyPriceValue >= paymentInfo.FeeTotal;
+                        }
+                    }
+
+                    if (isAuto)
+                    {
+                        var execCount = _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                        .SetColumns(a => new Grp_CreditCardPayment
+                        {
+                            IsAuditGM = 3, //自动审核通过
+                            AuditGMOperate = 4,
+                            AuditGMDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+                        })
+                        .Where(a => a.CId == paymentInfo.Id && a.CTable == 98 && a.DIId == paymentInfo.DiId)
+                        .ExecuteCommand();
+
+                        // 处理尾款审核
+                        var balanceAuditInfo = _sqlSugar.Queryable<Grp_DecreasePayments>()
+                            .Where(x => x.DiId == paymentInfo.DiId && x.IsDel == 0 && x.PriceName == paymentInfo.PriceName && x.PaymentType.Contains("尾款"))
+                            .First();
+                        if (balanceAuditInfo != null)
+                        {
+                            var balanceAudit = _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                                .SetColumns(a => new Grp_CreditCardPayment
+                                {
+                                    IsAuditGM = 3, //自动审核通过
+                                    AuditGMOperate = 4,
+                                    AuditGMDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+                                })
+                                .Where(a => a.CId == balanceAuditInfo.Id && a.CTable == 98 && a.DIId == balanceAuditInfo.DiId)
+                                .ExecuteCommand();
+                        }
+                        return (true, false);
                     }
                 }
             }
 
-            RollbackTran();
-            return new JsonView() { Code = 400, Msg = MsgTips.Fail };
+            return (false,true);
         }
 
         public async Task<Result> PostGroupNameAndEasy(DecreasePaymentsDto dto)
@@ -733,7 +993,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 #region 团组下拉框
 
 
-                List<Grp_GroupsTaskAssignment> grp_GroupsTaskAssignment = Query<Grp_GroupsTaskAssignment>(a => a.IsDel == 0 && a.UId == dto.UserId && a.CTId == dto.CTId).ToList();
+                var grp_GroupsTaskAssignment = Query<Grp_GroupsTaskAssignment>(a => a.IsDel == 0 && a.UId == dto.UserId && a.CTId == dto.CTId).ToList();
 
                 string DiId = "";
                 foreach (var item in grp_GroupsTaskAssignment)
@@ -750,7 +1010,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 }
 
                 string sql = string.Format(@"select Id,TourCode,TeamName,ClientName,VisitStartDate,VisitEndDate,VisitCountry,VisitDays,VisitPNumber from Grp_DelegationInfo where Id in({0}) and IsDel={1}", DiId, 0);
-                List<DelegationEasyView> grp_Delegations = _sqlSugar.SqlQueryable<DelegationEasyView>(sql).ToList();
+                var grp_Delegations = _sqlSugar.SqlQueryable<DelegationEasyView>(sql).ToList();
                 if (grp_Delegations.Count != 0)
                 {
                     int count = grp_Delegations.Count;
@@ -758,7 +1018,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                     if (totalPage == 0) totalPage = 1;
                     else totalPage = (int)Math.Ceiling((double)totalPage);
 
-                    List<DelegationEasyView> delegationEasyViews = new List<DelegationEasyView>();
+                    var delegationEasyViews = new List<DelegationEasyView>();
                     for (int i = 0; i < dto.PageSize; i++)
                     {
                         var RowIndex = i + (dto.PageIndex - 1) * dto.PageSize;
@@ -790,7 +1050,6 @@ namespace OASystem.Infrastructure.Repositories.Groups
             catch (Exception ex)
             {
                 return result = new Result() { Code = -2, Msg = "程序错误" };
-                throw;
             }
         }
 
@@ -817,6 +1076,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
 											dp.Price,
 											dp.Quantity,
 											dp.FeeTotal,
+											dp.PaymentType,
+											dp.PaymentPercent,
+											dp.ActualPaymentAmount,
 											dp.Currency,
 											dp.FilePath,
                                             dp.OTAOrderNo,

+ 79 - 7
OASystem/OASystem.Infrastructure/Repositories/Groups/FeeAuditRepository.cs

@@ -469,7 +469,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
                 }
                 ids = ids.Distinct().ToList();
 
-                //验证该费用是不是出行物资费用 是:审核 不是:不审核
+                // 验证该费用是否是出行物资费用 是:审核 否:不审核
                 if (!ids.Contains(dataId))
                 {
                     _view.Msg = $"该费用不属于出行物资,不执行自动审核!";
@@ -503,6 +503,9 @@ namespace OASystem.Infrastructure.Repositories.Groups
                         .ExecuteCommand();
                     if (ccpUpdate > 0)
                     {
+                        // 审核尾款
+                        ChangeFinalPaymentAuditStatus(diId,dataId,FinalPaymentAction.Approve);
+
                         _view.Code = 200;
                         _view.Msg = "自动审核执行成功";
                         return _view;
@@ -786,6 +789,68 @@ namespace OASystem.Infrastructure.Repositories.Groups
             return _view;
         }
 
+        /// <summary>
+        /// 变更尾款审核状态(自动通过或撤销)
+        /// </summary>
+        /// <param name="declarationId">GroupID</param>
+        /// <param name="expenseId">费用记录ID</param>
+        /// <param name="action">审核动作:Approve=自动通过, Revoke=撤销审核</param>
+        /// <param name="moduleId">模块编号,默认98</param>
+        public void ChangeFinalPaymentAuditStatus(int groupId, int expenseId, FinalPaymentAction action, int moduleId = 98)
+        {
+            var finalRecord = GetFinalPaymentRecord(groupId, expenseId);
+            if (finalRecord == null) return;
+
+            var (isAuditGM, auditGMOperate, auditGMDate) = action switch
+            {
+                FinalPaymentAction.Approve => (3, 4, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
+                FinalPaymentAction.Revoke => (0, 0, string.Empty),
+                _ => throw new ArgumentOutOfRangeException(nameof(action))
+            };
+
+            _sqlSugar.Updateable<Grp_CreditCardPayment>()
+                .SetColumns(it => it.IsAuditGM == isAuditGM)
+                .SetColumns(it => it.AuditGMOperate == auditGMOperate)
+                .SetColumns(it => it.AuditGMDate == auditGMDate)
+                .Where(s => s.DIId == groupId && s.CTable == moduleId && s.CId == finalRecord.Id)
+                .ExecuteCommand();
+        }
+
+        /// <summary>
+        /// 根据费用ID查找关联的尾款记录
+        /// </summary>
+        /// <param name="groupId"></param>
+        /// <param name="expenseId"></param>
+        /// <returns></returns>
+        private Grp_DecreasePayments GetFinalPaymentRecord(int groupId, int expenseId)
+        {
+            var currentExpense = _sqlSugar.Queryable<Grp_DecreasePayments>()
+                .Where(x => x.IsDel == 0 && x.DiId == groupId && x.Id == expenseId)
+                .First();
+
+            if (currentExpense == null) return null;
+
+            return _sqlSugar.Queryable<Grp_DecreasePayments>()
+                .Where(x => x.IsDel == 0
+                         && x.DiId == groupId
+                         && x.PriceName == currentExpense.PriceName
+                         && x.PaymentType.Contains("尾款"))
+                .First();
+        }
+
+
+        public enum FinalPaymentAction
+        {
+            /// <summary>
+            /// 自动通过审核
+            /// </summary>
+            Approve = 1,  
+            /// <summary>
+            /// 撤销审核
+            /// </summary>
+            Revoke = 2    
+        }
+
         /// <summary>
         /// hotel、op 撤销自动审核的数据
         /// </summary>
@@ -798,20 +863,27 @@ namespace OASystem.Infrastructure.Repositories.Groups
         /// <returns></returns>
         private bool QuashAudit(int type, int diId, int dataId)
         {
-            //撤销该条数据的自动审核 --> 该条数据的审核状态是自动审核  3 --> 0
+            // 撤销该条数据的自动审核 --> 该条数据的审核状态是自动审核  3 --> 0
             var ccpInfo = _sqlSugar.Queryable<Grp_CreditCardPayment>()
                 .Where(s => s.DIId == diId && s.CTable == type && s.CId == dataId && s.IsAuditGM == 3)
                 .First();
             if (ccpInfo != null)
             {
                 var ccpUpdate = _sqlSugar.Updateable<Grp_CreditCardPayment>()
-                               .SetColumns(it => it.IsAuditGM == 0)
-                               .SetColumns(it => it.AuditGMOperate == 0)
-                               .SetColumns(it => it.AuditGMDate == string.Empty)
-                               .Where(s => s.Id == ccpInfo.Id)
-                               .ExecuteCommand();
+                    .SetColumns(it => it.IsAuditGM == 0)
+                    .SetColumns(it => it.AuditGMOperate == 0)
+                    .SetColumns(it => it.AuditGMDate == string.Empty)
+                    .Where(s => s.Id == ccpInfo.Id)
+                    .ExecuteCommand();
                 if (ccpUpdate > 0)
                 {
+                    // 其他款项是撤销尾款审核
+                    if (type == 98)
+                    {
+                        // 撤销审核尾款
+                        ChangeFinalPaymentAuditStatus(diId, dataId, FinalPaymentAction.Revoke);
+                    }
+
                     return true;
                 }
             }

+ 19 - 0
OASystem/OASystem.Infrastructure/Tools/CommonFun.cs

@@ -1100,4 +1100,23 @@ public static class CommonFun
 
     #endregion
 
+    /// <summary>
+    /// string 转换 decimal,安全处理异常和百分号
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    public static decimal SafeParseDecimal(this string input)
+    {
+        if (string.IsNullOrWhiteSpace(input))
+            return 0;
+
+        return decimal.TryParse(
+            input.Replace("%", "").Trim(),
+            NumberStyles.Number,
+            CultureInfo.InvariantCulture,
+            out var result)
+            ? result
+            : 0;
+    }
+
 }