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

Merge branch 'develop' of http://132.232.92.186:3000/XinXiBu/OA2023 into develop

yuanrf 2 hete%!(EXTRA string=óta)
szülő
commit
acbfaadd98

+ 94 - 35
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -2,6 +2,7 @@
 using Aspose.Words;
 using Aspose.Words.Tables;
 using EyeSoft.Extensions;
+using NodaTime;
 using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
 using OASystem.API.OAMethodLib;
@@ -20,6 +21,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Data;
 using System.Diagnostics;
 using TencentCloud.Common;
+using static NodaTime.TimeZones.TzdbZone1970Location;
 using static OASystem.API.OAMethodLib.GeneralMethod;
 using static OASystem.API.OAMethodLib.JWTHelper;
 using static OpenAI.GPT3.ObjectModels.Models;
@@ -2328,11 +2330,23 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 }));
             }
 
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.Id == info.GroupId).FirstAsync();
+            // 设置国家、单位默认值
+            if (string.IsNullOrEmpty(info.EntryInfo.OriginUnit))
+            {
+                info.EntryInfo.OriginUnit = groupInfo?.ClientUnit ?? "";
+            }
+
+            if (info.EntryInfo.TargetCountry == null || info.EntryInfo.TargetCountry.Count < 1)
+            {
+                info.EntryInfo.TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.TeamName ?? "");
+            }
+
             var view = new
             {
                 info.Id,
-                info.InvName,
                 info.GroupId,
+                info.InvName,
                 info.AiCrawledDetails,
                 Entry = info.EntryInfo,
             };
@@ -2368,19 +2382,40 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
 
             var localInvDatas = new List<InvitationAIInfo>(); // 本地数据源(商邀资料)
-
+            var aiTasks = new List<CountryAIPormptInfo>();    // 记录:国家 -> 需要补齐的数量
             #region 本地数据源(商邀资料)
 
+            var datas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
+                .Where(x => x.IsDel == 0)
+                .Select(x => new { 
+                    x.Id,
+                    x.Country
+                })
+                .ToListAsync();
+
+            var decryptDatas = new List<Res_InvitationOfficialActivityData>();
+            foreach (var item in datas)
+            {
+                decryptDatas.Add(new() { 
+                    Id = item.Id,
+                    Country = AesEncryptionHelper.Decrypt(item.Country)
+                });
+            }
+
+            var localDatas = decryptDatas.Where(x=> !string.IsNullOrEmpty(x.Country) && entryInfo.TargetCountry.Contains(x.Country)).ToList();
+
+            var localDataIds = localDatas.Select(x => x.Id).ToList();
+
             var rawData = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
-                .Where(x => x.IsDel == 0 && x.Country.Equals(entryInfo.TargetCountry))
+                .Where(x => localDataIds.Contains(x.Id))
                 .ToListAsync();
 
-            // 解密
-            localInvDatas = rawData.AsParallel().AsOrdered().Select(item => new InvitationAIInfo
+            // 解密 
+            var localInvDecryptDatas = rawData.AsParallel().AsOrdered().Select(item => new InvitationAIInfo
             {
                 Guid = Guid.NewGuid().ToString("N"),
                 Source = 0, // 明确来源标识
-                Region = entryInfo.OriginUnit,
+                Region = AesEncryptionHelper.Decrypt(item.Country),
                 NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
                 Address = AesEncryptionHelper.Decrypt(item.Address),
                 Scope = AesEncryptionHelper.Decrypt(item.Field),
@@ -2391,15 +2426,26 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 Operator = operatorName,
             }).ToList();
 
+            // 国家数据条数
+            int targetPerCountry = invAiInfo.EntryInfo?.NeedCount ?? 10;
+            foreach (var countryName in invAiInfo.EntryInfo.TargetCountry)
+            {
+                var countryDatas = localInvDecryptDatas.Where(x => x.Region == countryName).Take(targetPerCountry).ToList();
+                if (countryDatas.Any())
+                {
+                    localInvDatas.AddRange(countryDatas);
+                    int gap = targetPerCountry - countryDatas.Count;
+                    if (gap > 0) aiTasks.Add(new() { Country = countryName, Count = gap });
+                }
+            }
+
             #endregion
 
             #region 混元AI 获取商邀资料
 
             var hunyuanAIInvDatas = new List<InvitationAIInfo>();
 
-            int aiNeedCount = Math.Max(0, totalTarget - localInvDatas.Count);
-
-            if (aiNeedCount > 0)
+            if (aiTasks != null && aiTasks.Count > 0)
             {
                 question = @$"# [SYSTEM_CONTEXT]
 - Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
@@ -2407,34 +2453,35 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 - Target Architecture: .NET 6 DTO Compatible Interface
 
 # [INPUT_CONFIG]
+- Tasks: {{{JsonConvert.SerializeObject(aiTasks)}}}
 - OriginUnit: {{{entryInfo.OriginUnit}}} 
-- TargetCountry: {{{entryInfo.TargetCountry}}}
 - Objective: {{{entryInfo.Objective}}}
-- DataCount: {{{aiNeedCount}}}
 - OtherConstraints: {{{entryInfo.OtherConstraints}}}
 # [ROLE_DEFINITION]
-你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
+你是一位精通全球跨境经贸、海外园区政策及 Tasks 中的国家本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
 
 # [BUSINESS_LOGIC_CoT]
 在生成结果前,请严格执行以下思维链拆解:
+1. **ScopePartitioning**: 遍历 Tasks 中的每个国家。
 1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
-2. **ParityMatching**: 检索 {{TargetCountry}} 境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
+2. **ParityMatching**: 针对每个国家,检索境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
 3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
-4. **DataSynthesis**: 针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
+4. **DataSynthesis**: 必须严格根据每个国家对应的 Count 生成数据条数。针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
 
 # [CONSTRAINTS_&_STANDARDS]
 - **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
 - **DataIntegrity**: 
-    - 必须输出精确的 {{DataCount}} 条记录
+    - 总条数必须等于 Tasks 中所有 Count 的总和
     - 优先级:Core (核心机构) > Backup (关联替代机构)。
 - **ValidationRules**:
-    - `Phone`: 必须包含 {{TargetCountry}} 国际区号(如 +856, +66 等)。
+    - `Phone`: 必须包含 Tasks 中的国家国际区号(如 +856, +66 等)。
     - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
     - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
 - **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
 
 # [INFORMATION_SCHEMA]
 请将结果填充至以下结构的 JSON 数组中:
+- Region:国家(必须与 Tasks 中的 Country 完全匹配)
 - NameCn: 单位名称(中文)
 - NameEn: 单位名称(英文)
 - Address: 详细地理位置(含省市区街道)
@@ -2515,7 +2562,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                             {
                                 x.Guid = Guid.NewGuid().ToString("N");
                                 x.Source = 1;
-                                x.Region = entryInfo.TargetCountry;
+                                //x.Region = entryInfo.TargetCountry;
                                 x.Operator = operatorName;
                                 x.OperatedAt = DateTime.Now;
                                 return x;
@@ -2568,7 +2615,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         public async Task<IActionResult> InvitationAISetPrompt(InvitationAISetPromptDto dto)
         {
             // 基础校验
-            if (string.IsNullOrWhiteSpace(dto.OriginUnit) || string.IsNullOrWhiteSpace(dto.TargetCountry))
+            if (string.IsNullOrWhiteSpace(dto.OriginUnit) || dto.TargetCountry == null || dto.TargetCountry.Count == 0)
                 return Ok(JsonView(false, "请传入有效的单位名称和国家!"));
 
             var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
@@ -2576,7 +2623,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 .Select(x => new { x.TeamName, x.VisitPurpose })
                 .FirstAsync();
 
-            string invName = $"{dto.OriginUnit}拜访{dto.TargetCountry}";
+            string invName = $"{dto.OriginUnit}拜访{string.Join("、",dto.TargetCountry)}";
 
             #region 数据库操作
 
@@ -2739,15 +2786,28 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var entryInfo = invAiInfo.EntryInfo;
 
+            // 记录:国家 -> 需要补齐的数量
+            var aiTasks = new List<CountryAIPormptInfo>();
+
+            // 单个国家数据总条数
+            int targetPerCountry = invAiInfo.EntryInfo?.NeedCount ?? 10;
+
+            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 });
+                }
+            }
+
             // 已搜索出的数据(AI 数据源)
             var hunyuanAIDatas = invAiInfo.AiCrawledDetails.Where(x => x.Source == 1).ToList() ?? new List<InvitationAIInfo>();
 
             // 当前企业领域
             string industryFocus = string.Empty;
 
-            // 动态计算 AI 需求缺口
-            int totalTarget = entryInfo.NeedCount;
-
             // 词条
             string question = string.Empty;
 
@@ -2757,13 +2817,11 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var hunyuanAIInvDatas = new List<InvitationAIInfo>();
 
-            int aiNeedCount = Math.Max(0, totalTarget - hunyuanAIDatas.Count);
-
             // 已存在数据名称整理
             var existingNames = new HashSet<string>(hunyuanAIDatas.Select(x => x.NameCn));
             entryInfo.OtherConstraints = $"请基于以下已存在的名称列表进行推荐,避免重复:{string.Join(", ", existingNames)}。{entryInfo.OtherConstraints}";
 
-            if (aiNeedCount > 0)
+            if (aiTasks != null && aiTasks.Count > 0)
             {
                 question = @$"# [SYSTEM_CONTEXT]
 - Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
@@ -2771,34 +2829,35 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 - Target Architecture: .NET 6 DTO Compatible Interface
 
 # [INPUT_CONFIG]
+- Tasks: {{{JsonConvert.SerializeObject(aiTasks)}}}
 - OriginUnit: {{{entryInfo.OriginUnit}}} 
-- TargetCountry: {{{entryInfo.TargetCountry}}}
 - Objective: {{{entryInfo.Objective}}}
-- DataCount: {{{aiNeedCount}}}
 - OtherConstraints: {{{entryInfo.OtherConstraints}}}
 # [ROLE_DEFINITION]
-你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
+你是一位精通全球跨境经贸、海外园区政策及 Tasks 中的国家本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
 
 # [BUSINESS_LOGIC_CoT]
 在生成结果前,请严格执行以下思维链拆解:
+1. **ScopePartitioning**: 遍历 Tasks 中的每个国家。
 1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
-2. **ParityMatching**: 检索 {{TargetCountry}} 境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
+2. **ParityMatching**: 针对每个国家,检索境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
 3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
-4. **DataSynthesis**: 针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
+4. **DataSynthesis**: 必须严格根据每个国家对应的 Count 生成数据条数。针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
 
 # [CONSTRAINTS_&_STANDARDS]
 - **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
 - **DataIntegrity**: 
-    - 必须输出精确的 {{DataCount}} 条记录
+    - 总条数必须等于 Tasks 中所有 Count 的总和
     - 优先级:Core (核心机构) > Backup (关联替代机构)。
 - **ValidationRules**:
-    - `Phone`: 必须包含 {{TargetCountry}} 国际区号(如 +856, +66 等)。
+    - `Phone`: 必须包含 Tasks 中的国家国际区号(如 +856, +66 等)。
     - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
     - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
 - **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
 
 # [INFORMATION_SCHEMA]
 请将结果填充至以下结构的 JSON 数组中:
+- Region:国家(必须与 Tasks 中的 Country 完全匹配)
 - NameCn: 单位名称(中文)
 - NameEn: 单位名称(英文)
 - Address: 详细地理位置(含省市区街道)
@@ -2879,7 +2938,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                             {
                                 x.Guid = Guid.NewGuid().ToString("N");
                                 x.Source = 1;
-                                x.Region = entryInfo.TargetCountry;
+                                //x.Region = entryInfo.TargetCountry;
                                 x.Operator = operatorName;
                                 x.OperatedAt = DateTime.Now;
                                 return x;
@@ -3456,8 +3515,8 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                         var req = new EmailRequestDto()
                         {
                             ToEmails = new List<string> { item.Email },
-                            CcEmails = new List<string> { userInfo.Email },
-                            BccEmails = new List<string> { userInfo.Email },
+                            //CcEmails = new List<string> { userInfo.Email },
+                            //BccEmails = new List<string> { userInfo.Email },
                             Subject = item.EmailInfo.EmailTitle,
                             Body = item.EmailInfo.EmailContent,
                             Files = Array.Empty<IFormFile>()

+ 1 - 1
OASystem/OASystem.Domain/Dtos/Resource/InvitationAI.cs

@@ -20,7 +20,7 @@ namespace OASystem.Domain.Dtos.Resource
         /// <summary>
         /// 出访国家
         /// </summary>
-        public string TargetCountry { get; set; }
+        public List<string> TargetCountry { get; set; }
         /// <summary>
         /// 备注信息
         /// </summary>

+ 9 - 2
OASystem/OASystem.Domain/Entities/Resource/Res_InvitationAI.cs

@@ -135,15 +135,16 @@ namespace OASystem.Domain.Entities.Resource
         /// <summary>
         /// 拜访国家
         /// </summary>
-        public string TargetCountry { get; set; }
+        public List<string> TargetCountry { get; set; } = new List<string>();
         /// <summary>
         /// 出访目的
         /// </summary>
         public string Objective { get; set; }
         /// <summary>
         /// 数据条数
+        /// 每个国家获取数据总条数,默认10条
         /// </summary>
-        public int NeedCount { get; set; } = 20;
+        public int NeedCount { get; set; } = 10;
         /// <summary>
         /// 其他规则
         /// </summary>
@@ -200,4 +201,10 @@ namespace OASystem.Domain.Entities.Resource
         public string Subject { get; set; }
         public string Content { get; set; }
     }
+
+
+    public class CountryAIPormptInfo {
+        public string Country { get; set; }
+        public int Count { get; set; }
+    }
 }