소스 검색

新增关键字搜索与波兰签证功能支持

新增接团信息关键字搜索功能,支持多种匹配模式(关键值、符号分割、单字匹配),并基于原生 SQL 查询实现稳定性。
扩展申根国和欧盟国家列表,新增波兰签证申请表生成功能,包括 PDF 表单字段填充、酒店与邀请人信息处理、费用支付信息处理、停留天数计算等。
优化代码结构,新增辅助实体类 `VisaDateView` 和 `SSGDetails`,提升代码可读性与维护性。
更新 `appsettings.json`,新增市场部人员信息。
Lyyyi 15 시간 전
부모
커밋
213bc74970
2개의 변경된 파일805개의 추가작업 그리고 3개의 파일을 삭제
  1. 801 3
      OASystem/OASystem.Api/Controllers/GroupsController.cs
  2. 4 0
      OASystem/OASystem.Api/appsettings.json

+ 801 - 3
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -48,6 +48,7 @@ using OASystem.Infrastructure.Tools;
 using Quartz.Logging;
 using Quartz.Util;
 using SQLitePCL;
+using SqlSugar;
 using SqlSugar.Extensions;
 using System.Collections;
 using System.ComponentModel.DataAnnotations;
@@ -1164,6 +1165,337 @@ namespace OASystem.API.Controllers
 
         }
 
+        /// <summary>
+        ///  接团信息列表 关键字输入提示
+        /// </summary>
+        /// <param name="keyword">关键字</param>
+        /// <returns></returns>
+        [HttpGet]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> GroupItemKeywordSearch(string keyword)
+        {
+
+            if (string.IsNullOrWhiteSpace(keyword))
+            {
+                var result = await GetDefaultResultsAsync();
+                return Ok(JsonView(result));
+            }
+
+            // 分析搜索模式
+            var searchAnalysis = AnalyzeSearchPattern(keyword);
+
+            if (!searchAnalysis.HasSearchContent)
+            {
+                var result = await GetDefaultResultsAsync();
+                return Ok(JsonView(result));
+            }
+
+            // 使用方法4:原生SQL查询(最稳定)
+            var results = await SearchWithNativeSql(searchAnalysis);
+            var results1 = CalculateUnifiedMatchScore(results, searchAnalysis);
+            return Ok(JsonView(true, $"搜索成功", results1, results1.Count));
+          
+        }
+
+        #region 搜索辅助方法
+
+        private List<GroupInfoWithScore> CalculateUnifiedMatchScore(List<GroupInfoWithScore> results, SearchAnalysis analysis)
+        {
+            return results.Select(info => new
+            {
+                Info = info,
+                Score = CalculateComprehensiveMatchScore(info, analysis)
+            })
+            .Where(x => x.Score > 0)
+            .OrderByDescending(x => x.Score)
+            //.ThenByDescending(x => x.Info.CreateTime)
+            .Select(x =>
+            {
+                x.Info.MatchScore = x.Score;
+                return x.Info;
+            })
+            .ToList();
+        }
+
+        private int CalculateComprehensiveMatchScore(GroupInfoWithScore info, SearchAnalysis analysis)
+        {
+            int totalScore = 0;
+
+            var targetFields = new[]
+            {
+                new { Value = info.TeamName, Weight = 10 },
+                new { Value = info.ClientUnit, Weight = 8 },
+                new { Value = info.ClientName, Weight = 6 }
+            };
+
+            // 关键值匹配得分
+            foreach (var field in targetFields)
+            {
+                foreach (var keyValue in analysis.KeyValues)
+                {
+                    totalScore += CalculateFieldMatchScore(field.Value, keyValue, field.Weight);
+                }
+            }
+
+            // 符号分割关键字匹配得分
+            foreach (var field in targetFields)
+            {
+                foreach (var segment in analysis.SymbolSegments)
+                {
+                    var cleanSegment = Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
+                    if (!string.IsNullOrEmpty(cleanSegment))
+                    {
+                        totalScore += CalculateFieldMatchScore(field.Value, cleanSegment, field.Weight);
+                    }
+                }
+            }
+
+            // 单字匹配得分
+            foreach (var field in targetFields)
+            {
+                foreach (var singleChar in analysis.SingleChars)
+                {
+                    totalScore += CalculateSingleCharScore(field.Value, singleChar, (int)(field.Weight * 0.3));
+                }
+            }
+
+            return totalScore;
+        }
+
+        private int CalculateFieldMatchScore(string fieldValue, string keyword, int baseWeight)
+        {
+            if (string.IsNullOrEmpty(fieldValue) || string.IsNullOrEmpty(keyword))
+                return 0;
+
+            if (fieldValue.Contains(keyword))
+            {
+                int score = baseWeight;
+
+                if (fieldValue.Equals(keyword))
+                    score += 15;
+                else if (fieldValue.StartsWith(keyword))
+                    score += 10;
+                else if (fieldValue.EndsWith(keyword))
+                    score += 5;
+
+                return score;
+            }
+
+            return 0;
+        }
+
+        private int CalculateSingleCharScore(string fieldValue, char singleChar, int baseWeight)
+        {
+            if (string.IsNullOrEmpty(fieldValue))
+                return 0;
+
+            int count = fieldValue.Count(c => c == singleChar);
+            int score = count * baseWeight;
+
+            if (fieldValue.Length > 0 && fieldValue[0] == singleChar)
+            {
+                score += baseWeight * 2;
+            }
+
+            return score;
+        }
+
+        /// <summary>
+        /// 分析搜索模式 - 关键值查询改为单字查询
+        /// </summary>
+        private SearchAnalysis AnalyzeSearchPattern(string keyword)
+        {
+            var analysis = new SearchAnalysis { OriginalKeyword = keyword };
+
+            // 1. 检查是否包含特殊符号
+            if (ContainsSpecialSymbols(keyword))
+            {
+                analysis.HasSpecialSymbols = true;
+                analysis.SymbolSegments = SplitBySpecialSymbols(keyword);
+            }
+
+            // 2. 将所有输入字符都作为单字处理
+            var cleanKeyword = Regex.Replace(keyword, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
+
+            if (!string.IsNullOrEmpty(cleanKeyword))
+            {
+                // 将所有字符都作为单字处理
+                foreach (char c in cleanKeyword)
+                {
+                    analysis.SingleChars.Add(c);
+                }
+
+                analysis.IsSingleChar = true;
+
+                // 同时将长度>1的连续字符也作为符号分割段处理(为了兼容性)
+                if (cleanKeyword.Length > 1 && !analysis.HasSpecialSymbols)
+                {
+                    analysis.SymbolSegments.Add(cleanKeyword);
+                }
+            }
+
+            // 3. 如果包含符号,也从分割结果中提取单字
+            if (analysis.HasSpecialSymbols)
+            {
+                foreach (var segment in analysis.SymbolSegments)
+                {
+                    var cleanSegment = Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
+                    if (!string.IsNullOrEmpty(cleanSegment))
+                    {
+                        // 将分割段中的每个字符都作为单字
+                        foreach (char c in cleanSegment)
+                        {
+                            if (!analysis.SingleChars.Contains(c))
+                            {
+                                analysis.SingleChars.Add(c);
+                            }
+                        }
+                    }
+                }
+            }
+
+            // 去重
+            analysis.SingleChars = analysis.SingleChars.Distinct().ToList();
+            analysis.SymbolSegments = analysis.SymbolSegments.Distinct().ToList();
+
+            return analysis;
+        }
+
+        private bool ContainsSpecialSymbols(string keyword)
+        {
+            var specialSymbols = new[] { ' ', ',', ',', ';', ';', '、', '/', '\\', '|', '-', '_' };
+            return keyword.Any(c => specialSymbols.Contains(c));
+        }
+
+        private List<string> SplitBySpecialSymbols(string keyword)
+        {
+            var separators = new[] { ' ', ',', ',', ';', ';', '、', '/', '\\', '|', '-', '_' };
+            return keyword.Split(separators, StringSplitOptions.RemoveEmptyEntries)
+                         .Select(s => s.Trim())
+                         .Where(s => !string.IsNullOrEmpty(s))
+                         .ToList();
+        }
+
+        /// <summary>
+        /// 使用原生SQL条件(最稳定)
+        /// </summary>
+        private async Task<List<GroupInfoWithScore>> SearchWithNativeSql(SearchAnalysis analysis)
+        {
+            var whereConditions = new List<string>();
+            var parameters = new List<SugarParameter>();
+
+            // 构建关键值条件
+            foreach (var keyValue in analysis.KeyValues)
+            {
+                whereConditions.Add($"(TeamName LIKE @keyValue{parameters.Count} OR ClientUnit LIKE @keyValue{parameters.Count} OR ClientName LIKE @keyValue{parameters.Count})");
+                parameters.Add(new SugarParameter($"@keyValue{parameters.Count}", $"%{keyValue}%"));
+            }
+
+            // 构建符号分割条件
+            foreach (var segment in analysis.SymbolSegments)
+            {
+                var cleanSegment = Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
+                if (!string.IsNullOrEmpty(cleanSegment))
+                {
+                    whereConditions.Add($"(TeamName LIKE @segment{parameters.Count} OR ClientUnit LIKE @segment{parameters.Count} OR ClientName LIKE @segment{parameters.Count})");
+                    parameters.Add(new SugarParameter($"@segment{parameters.Count}", $"%{cleanSegment}%"));
+                }
+            }
+
+            // 构建单字条件
+            foreach (var singleChar in analysis.SingleChars)
+            {
+                var charStr = singleChar.ToString();
+                whereConditions.Add($"(TeamName LIKE @char{parameters.Count} OR ClientUnit LIKE @char{parameters.Count} OR ClientName LIKE @char{parameters.Count})");
+                parameters.Add(new SugarParameter($"@char{parameters.Count}", $"%{charStr}%"));
+            }
+
+            // 构建SQL
+            var whereClause = whereConditions.Any()
+                ? "AND (" + string.Join(" OR ", whereConditions) + ")"
+                : "";
+
+            var sql = $@"
+            SELECT * FROM Grp_DelegationInfo 
+            WHERE IsDel = 0 {whereClause}
+            ORDER BY CreateTime DESC";
+
+            var result = await _sqlSugar.Ado.SqlQueryAsync<Grp_DelegationInfo>(sql, parameters);
+
+            return result == null 
+                ? new List<GroupInfoWithScore>() 
+                : result.Select(x => new GroupInfoWithScore() { Id = x.Id, TeamName = x.TeamName, ClientUnit = x.ClientUnit, ClientName = x.ClientName }).ToList();
+        }
+
+        /// <summary>
+        /// 获取默认结果
+        /// </summary>
+        private async Task<List<GroupInfoWithScore>> GetDefaultResultsAsync()
+        {
+            return await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                           .Where(x => x.IsDel == 1)
+                           .OrderByDescending(x => x.CreateTime)
+                           .Select(x => new GroupInfoWithScore() { Id = x.Id, TeamName = x.TeamName, ClientUnit = x.ClientUnit, ClientName = x.ClientName })
+                           .ToListAsync();
+        }
+
+        private class GroupInfoWithScore
+        {
+            public int Id { get; set; }
+            public string TeamName { get; set; }
+            public string ClientUnit { get; set; }
+            public string ClientName { get; set; }
+            public int MatchScore { get; set; }
+        }
+
+        /// <summary>
+        /// 搜索分析结果
+        /// </summary>
+        public class SearchAnalysis
+        {
+            /// <summary>
+            /// 原始关键词
+            /// </summary>
+            public string OriginalKeyword { get; set; }
+
+            /// <summary>
+            /// 是否有关键值
+            /// </summary>
+            public bool HasKeyValue { get; set; }
+
+            /// <summary>
+            /// 关键值列表
+            /// </summary>
+            public List<string> KeyValues { get; set; } = new List<string>();
+
+            /// <summary>
+            /// 是否有特殊符号
+            /// </summary>
+            public bool HasSpecialSymbols { get; set; }
+
+            /// <summary>
+            /// 符号分割的段
+            /// </summary>
+            public List<string> SymbolSegments { get; set; } = new List<string>();
+
+            /// <summary>
+            /// 是否是单字
+            /// </summary>
+            public bool IsSingleChar { get; set; }
+
+            /// <summary>
+            /// 单字列表
+            /// </summary>
+            public List<char> SingleChars { get; set; } = new List<char>();
+
+            /// <summary>
+            /// 是否有搜索内容
+            /// </summary>
+            public bool HasSearchContent => HasKeyValue || HasSpecialSymbols || IsSingleChar;
+        }
+
+        #endregion
+
         /// <summary>
         ///  接团信息列表 Page
         /// </summary>
@@ -4191,16 +4523,17 @@ FROM
             { 2, "日本" },
             { 3, "韩国" },
             { 4, "瑞典" },
+            { 5, "波兰" },
         };
 
         //申根国
-        private readonly string[] _shenGenGuos = new string[] { "奥地利", "比利时", "丹麦", "芬兰", "法国", "德国", "冰岛", "意大利", "希腊", "卢森堡", "荷兰", "挪威", "葡萄牙", "西班牙",
+        private readonly static string[] _shenGenGuos = new string[] { "奥地利", "比利时", "丹麦", "芬兰", "法国", "德国", "冰岛", "意大利", "希腊", "卢森堡", "荷兰", "挪威", "葡萄牙", "西班牙",
                                               "瑞典", "匈牙利", "捷克", "斯洛伐克", "斯洛文尼亚","波兰","爱沙尼亚","拉脱维亚","立陶宛","马耳他","瑞士","列支敦士登" };
         //欧盟国家
-        private readonly string[] _eucountries = new string[] { "奥地利", "比利时", "丹麦", "芬兰", "法国", "德国", "意大利", "希腊", "卢森堡", "荷兰",  "葡萄牙", "西班牙","爱尔兰","罗马尼亚",
+        private readonly static string[] _eucountries = new string[] { "奥地利", "比利时", "丹麦", "芬兰", "法国", "德国", "意大利", "希腊", "卢森堡", "荷兰",  "葡萄牙", "西班牙","爱尔兰","罗马尼亚",
                                               "保加利亚","匈牙利", "捷克", "斯洛伐克", "斯洛文尼亚","波兰","爱沙尼亚","拉脱维亚","立陶宛","马耳他","瑞士","列支敦士登" };
         //欧洲国家
-        private readonly string[] _aBC = new string[] { "英国", "爱尔兰" };
+        private readonly static string[] _aBC = new string[] { "英国", "爱尔兰" };
 
         /// <summary>
         ///  团组&签证 New
@@ -5475,6 +5808,41 @@ FROM
                         #endregion
                     }
 
+                    break;
+                case 5: // 波兰
+
+                    visaTypeName = $"波兰签证申请表";
+                    currTempPath = $"{tempPath}{visaTypeName}.pdf";
+
+                    var polandSavePath = $"{fileServerPath}{visaTypeName}/";
+                    if (!Directory.Exists(polandSavePath)) Directory.CreateDirectory(polandSavePath);
+
+                    if (!System.IO.File.Exists(currTempPath))
+                    {
+                        return Ok(JsonView(false, $"{currTempPath}模板不存在!"));
+                    }
+
+                    foreach (var custInfo in groupCustInfos)
+                    {
+                        EncryptionProcessor.DecryptProperties(custInfo);
+
+                        var visaData = new VisaDateView() {
+                            groupInfo = groupInfo,
+                            idInfo = certInfos.FirstOrDefault(x => x.DcId == custInfo.Id && x.PassportType == 773),
+                            passportInfo = certInfos.FirstOrDefault(x => x.DcId == custInfo.Id && x.PassportType == 774),
+                            custInfo = custInfo,
+                            schoolInfos = schoolInfos.Where(x => x.DcId == custInfo.Id).ToList(),
+                            familyInfos = familyInfos.Where(x => x.DcId == custInfo.Id).ToList(),
+                            companyInfos = companyInfos.Where(x => x.DcId == custInfo.Id).ToList()
+                        };
+
+                        string outFilePath = CreatePolandVisaPdf(visaData, polandSavePath, currTempPath);
+
+                        filePaths.Add(outFilePath);
+                        #region 生成文件
+                        #endregion
+                    }
+
                     break;
                 default:
                     return Ok(JsonView(false, "正在开发中,请等待。。。"));
@@ -5528,9 +5896,439 @@ FROM
             }
         }
 
+
+        /// <summary>
+        /// 创建PDF
+        /// </summary>
+        /// <param name="data"></param>
+        /// <param name="currSavePath"></param>
+        /// <param name="currTempPath"></param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        private string CreatePolandVisaPdf(VisaDateView data, string currSavePath,string currTempPath)
+        {
+            var groupInfo = data.groupInfo;
+            var groupId = groupInfo.Id;
+            var idInfo = data.idInfo;
+            var passportInfo = data.passportInfo;
+            var custInfo = data.custInfo;
+            var currSchoolInfos = data.schoolInfos;
+            var currFamilyInfos = data.familyInfos;
+            var currCompanyInfos = data.companyInfos;
+
+            string outputPath = $"{currSavePath}{custInfo.LastName}{custInfo.FirstName}.pdf";
+
+            PdfReader reader = null;
+            FileStream templateStream = null;
+            FileStream outputStream = null;
+            PdfStamper stamper = null;
+
+            try
+            {
+                //读取模板文件
+                templateStream = new FileStream(currTempPath, FileMode.Open, FileAccess.Read, FileShare.Read);
+                reader = new PdfReader(templateStream);
+
+                //创建输出文件
+                outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
+                stamper = new PdfStamper(reader, outputStream);
+
+                AcroFields formFields = stamper.AcroFields;
+
+                #region 设置文本值
+
+                SetFieldValue(formFields, "1.LastName", custInfo.LastName);      //姓
+                SetFieldValue(formFields, "2.BirthLastName", custInfo.LastName); // 出生时姓氏
+                SetFieldValue(formFields, "3.FirstName", custInfo.FirstName);    // 名
+
+                // 出生日期(日-月-年)
+                string idCard = idInfo.CertNo;
+                string birthday = string.Empty;
+                if (!string.IsNullOrEmpty(idCard))
+                {
+                    string year = idCard.Substring(6, 4);
+                    string month = idCard.Substring(10, 2);
+                    string day = idCard.Substring(12, 2);
+                    birthday = day + "-" + month + "-" + year;
+                }
+                SetFieldValue(formFields, "4.Birthday", birthday);
+
+                string currentNationality = custInfo.BirthProvince + custInfo.BirthCity;
+                SetFieldValue(formFields, "CurrentNationality", currentNationality); // 出生地
+                SetFieldValue(formFields, "CountryBirth", "中国"); // 出生国
+                SetFieldValue(formFields, "CurrentNationalityCountry", "中国"); // 现国籍
+                SetFieldValue(formFields, "NationalityBirth", ""); // 出生时国籍,如与现国籍不同
+                SetFieldValue(formFields, "OtherNationalities", ""); // 其他国籍
+
+                #region 性别处理
+                //清除性别
+                SetFieldValue(formFields, "Male", "Off"); // 性别 - 男
+                SetFieldValue(formFields, "Female", "Off"); // 性别 - 女
+                                                            //设置性别
+                if (custInfo.Sex == 0) //男
+                {
+                    SetFieldValue(formFields, "Male", "Yes"); // 性别 - 男
+                }
+                else if (custInfo.Sex == 1) //女
+                {
+                    SetFieldValue(formFields, "Female", "Yes"); // 性别 - 女
+                }
+
+                #endregion
+
+                #region 婚姻状况处理
+                //清除婚姻状况
+                SetFieldValue(formFields, "Single", "Off"); // 婚姻状况 - 未婚
+                SetFieldValue(formFields, "Married", "Off"); // 婚姻状况 - 已婚
+                SetFieldValue(formFields, "RegisteredPartnership", "Off"); // 婚姻状况 - 注册伴侣关系
+                SetFieldValue(formFields, "Separated", "Off"); // 婚姻状况 - 分居
+                SetFieldValue(formFields, "Divorced", "Off"); // 婚姻状况 - 离婚
+                SetFieldValue(formFields, "Widow(er)", "Off"); // 婚姻状况 - 丧偶
+                SetFieldValue(formFields, "OtherCivilStatus", "Off"); // 婚姻状况 - 其它
+                SetFieldValue(formFields, "CivilStatusOther", ""); // 婚姻状况 - 其它(请注明)
+                                                                   //设置婚姻状况
+                switch (custInfo.Marriage)
+                {
+                    case 1: // 未婚
+                        SetFieldValue(formFields, "Single", "Yes");
+                        break;
+                    case 2: // 已婚
+                        SetFieldValue(formFields, "Married", "Yes");
+                        break;
+                    case 3: // 离异
+                        SetFieldValue(formFields, "Divorced", "Yes");
+                        break;
+                    case 4: // 离异
+                        SetFieldValue(formFields, "Widow(er)", "Yes");
+                        break;
+                }
+
+                #endregion
+
+                //亲权人(如是未成年申请人 )/合法监护人(姓名、住址,如与申请人不同) 电话号码、电子邮件及国籍
+                SetFieldValue(formFields, "ParentalAuthority", "");
+
+                SetFieldValue(formFields, "NationalIdentityNumber", idCard);  //公民身份证号码
+
+                #region 护照(旅行证件类型)处理
+                //清除值
+                SetFieldValue(formFields, "OrdinaryPassport", "Off");  //旅行证件类型 - 普通护照
+                SetFieldValue(formFields, "DiplomaticPassport", "Off");  //旅行证件类型 - 外交护照
+                SetFieldValue(formFields, "ServicePassport", "Off");  //旅行证件类型 - 公务护照
+                SetFieldValue(formFields, "OfficialPassport", "Off");  //旅行证件类型 - 因公护照
+                SetFieldValue(formFields, "SpecialPassport", "Off");  //旅行证件类型 - 特殊护照
+                SetFieldValue(formFields, "OtherTravel", "Off");  //旅行证件类型 - 其它旅行证件
+                SetFieldValue(formFields, "OtherTravelDesc", "");     //旅行证件类型 - 其它旅行证件(请注明)
+                                                                      //设置值
+                SetFieldValue(formFields, "OrdinaryPassport", "Yes");  //旅行证件类型 - 普通护照
+                if (passportInfo != null)
+                {
+                    SetFieldValue(formFields, "TravelNumber", passportInfo.CertNo);  //旅行证件编号
+                    SetFieldValue(formFields, "IssueDate", passportInfo.IssueDt?.ToString("yyyy-MM-dd") ?? "");  //签发日期
+                    SetFieldValue(formFields, "ValidUntil", passportInfo.ExpiryDt?.ToString("yyyy-MM-dd") ?? "");  //有效期至
+                    SetFieldValue(formFields, "IssuedCountry", passportInfo.Country);  //签发(国)
+                }
+                #endregion
+
+                //如有家庭成员为欧盟、欧洲经济区、瑞士或受益于退欧协议的英国公民的,请填写该家庭成员的个人信息
+                SetFieldValue(formFields, "LastName_om", "");  //姓
+                SetFieldValue(formFields, "FirstName_om", "");  //名
+                SetFieldValue(formFields, "BirthDate_om", "");  //出生日期(日-月-年)
+                SetFieldValue(formFields, "Nationality_om", "");  //国籍
+                SetFieldValue(formFields, "TravelNuber_om", "");  //旅行证件或身分证编号
+
+                //申请人与欧盟、欧洲经济区、瑞士或受益于退欧协议的英国公民的关系 
+                SetFieldValue(formFields, "Spouse_om", "");  //配偶
+                SetFieldValue(formFields, "RegisteredPartnership_om", "");  //注册伴侣
+                SetFieldValue(formFields, "Child_om", "");  //子女
+                SetFieldValue(formFields, "DependentAscendant_om", "");  //受养人
+                SetFieldValue(formFields, "Grandchild_om", "");  //孙子女 
+                SetFieldValue(formFields, "FamilyOther_om", "");  //其他 
+                SetFieldValue(formFields, "FamilyOtherDesc_om", "");  //其他(说明)
+
+                //申请人住址及电子邮件
+                string applicantHomeAddress = $"{custInfo.Address} {custInfo.Email}";
+                SetFieldValue(formFields, "ApplicantHomeAddress", applicantHomeAddress);  //申请人住址及电子邮件
+                SetFieldValue(formFields, "TelephoneNo", custInfo.Tel);  //电话号码
+
+                //是否居住在现时国籍以外的国家
+                SetFieldValue(formFields, "AnotherCountryNo", "Yes");  //否
+                SetFieldValue(formFields, "AnotherCountryYes", "");  //是。
+                SetFieldValue(formFields, "ResidencePermit", "");  //居住许可或同等证
+                SetFieldValue(formFields, "Number", "");  //编号
+                SetFieldValue(formFields, "ValidUntils", "");  //有效期至
+
+                SetFieldValue(formFields, "CurrentOccupation", custInfo.Job);  //当前职业 
+
+                //工作单位名称,地址和电话,学生填写学校名称及地址
+                var currCompanyInfo = currCompanyInfos.FirstOrDefault();
+                string employerInfoStr = $"{currCompanyInfo?.Company ?? ""} {currCompanyInfo?.CompanyAddress ?? ""} {currCompanyInfo?.Phone ?? ""}";
+                SetFieldValue(formFields, "EmployerInfo", employerInfoStr);
+
+                #region 旅行的目的
+                //清空值
+                SetFieldValue(formFields, "Tourism", "Off");  //旅游
+                SetFieldValue(formFields, "Business", "Off");  //商务
+                SetFieldValue(formFields, "VisitingFamily", "Off");  //探亲访友
+                SetFieldValue(formFields, "Cultural", "Off");  //文化
+                SetFieldValue(formFields, "Sports", "Off");  //体育
+                SetFieldValue(formFields, "OfficialVisit", "Off");  //官方访问
+                SetFieldValue(formFields, "MedicalReasons", "Off");  //医疗
+                SetFieldValue(formFields, "Study", "Off");  //学习
+                SetFieldValue(formFields, "AirportTransit", "Off");  //机场过境
+                SetFieldValue(formFields, "Other", "Off");  //其它
+                SetFieldValue(formFields, "OtherDesc", "");  //其它 (请注明)
+
+                //设置值
+                if (groupInfo.VisitPurpose.contains("旅游")) SetFieldValue(formFields, "Tourism", "Yes");  //旅游
+                if (groupInfo.VisitPurpose.contains("商务")) SetFieldValue(formFields, "Business", "Yes");  //商务
+                if (groupInfo.VisitPurpose.contains("探亲访友")) SetFieldValue(formFields, "VisitingFamily", "Yes");  //探亲访友
+                if (groupInfo.VisitPurpose.contains("文化")) SetFieldValue(formFields, "Cultural", "Yes");  //文化
+                if (groupInfo.VisitPurpose.contains("体育")) SetFieldValue(formFields, "Sports", "Yes");  //体育
+                if (groupInfo.VisitPurpose.contains("官方访问")) SetFieldValue(formFields, "OfficialVisit", "Yes");  //官方访问
+                if (groupInfo.VisitPurpose.contains("医疗")) SetFieldValue(formFields, "MedicalReasons", "Yes");  //医疗
+                if (groupInfo.VisitPurpose.contains("学习")) SetFieldValue(formFields, "Study", "Yes");  //学习
+                if (groupInfo.VisitPurpose.contains("机场过境")) SetFieldValue(formFields, "AirportTransit", "Yes");  //机场过境
+
+                #endregion
+
+                SetFieldValue(formFields, "AdditionalInfo", "");  //有关停留原因的补充信息
+
+                string[] RD_country = new string[] { };  //接收处理的国家
+
+                string firstEntryLabel = string.Empty; //首入申根国标签
+                string membeDescLabel = string.Empty; //申根国目的地
+                string stopDays = string.Empty; //停留天数
+                if (!string.IsNullOrEmpty(groupInfo.VisitCountry))
+                {
+                    List<string> SGGNames = new List<string>();//接收处理后的国家  是申根国
+                    if (groupInfo.VisitCountry.Contains(","))
+                        RD_country = groupInfo.VisitCountry.Split(',');
+                    else if (groupInfo.VisitCountry.Contains(' '))
+                        RD_country = groupInfo.VisitCountry.Split(' ');
+                    else if (groupInfo.VisitCountry.Contains(","))
+                        RD_country = groupInfo.VisitCountry.Split(',');
+                    else if (groupInfo.VisitCountry.Contains("、"))
+                        RD_country = groupInfo.VisitCountry.Split('、');
+                    else if (groupInfo.VisitCountry.Contains("|"))
+                        RD_country = groupInfo.VisitCountry.Split('|');
+
+                    //判断是否是申根国家
+                    for (int i = 0; i < RD_country.Length; i++)
+                    {
+                        if (RD_country[i].ToString() != null)
+                        {
+                            //在申根国家中筛选 该国家是否是申根国家
+                            for (int j = 0; j < _shenGenGuos.Length; j++)
+                            {
+                                if (RD_country[i] == _shenGenGuos[j])
+                                    SGGNames.Add(_shenGenGuos[j]); //添加申根国
+                            }
+                        }
+                    }
+
+                    //目的地国是否有申根国
+                    if (SGGNames.Count > 0)
+                    {
+                        firstEntryLabel = SGGNames.First();
+                        membeDescLabel = string.Join(",", SGGNames.ToArray());
+
+                        //接收生根国详细信息  存储顺序 起飞日期:Day 起飞时刻:StartTime  抵达日期:ArrivedDate 到达时刻:EndTime
+                        List<SSGDetails> ListSSGDetails = new List<SSGDetails>();
+
+                        //处理申根国家 预定到达时间  预定离开时间 预定停留天数   
+                        for (int i = 0; i < SGGNames.Count; i++)
+                        {
+                            //在相同的国家下  得到该航班的信息  //抵达时间 起飞日期:Day   起飞时刻:StartTime  到达时刻:EndTime  抵达日期:ArrivedDate 飞行时间:FlightTime  航司  Fliagtcode  入境口岸 
+                            var atbc = GeneralMethod.GetTableByBlackCode(groupId);
+                            //ResidenceTime1 机票三字码信息
+                            if (atbc != null)
+                            {
+                                string HL_Three = ""; //三字码  Three  
+
+                                for (int j = 0; j < atbc.Rows.Count; j++)
+                                {
+                                    HL_Three = atbc.Rows[j]["Three"].ToString();
+
+                                    if (!string.IsNullOrEmpty(HL_Three))
+                                    {
+                                        string threeCodeTwo = HL_Three.Substring(3);
+                                        var tc = _sqlSugar.Queryable<Res_ThreeCode>().Where(x => x.Three == threeCodeTwo).First();
+                                        if (tc != null)
+                                        {
+                                            //首入申根国时间
+                                            if (SGGNames[i] == tc.Country)
+                                            {
+                                                SSGDetails SSGDetails = new SSGDetails()
+                                                {
+                                                    Three = atbc.Rows[j]["Three"].ToString(),           //三字码
+                                                    Fliagtcode = atbc.Rows[j]["Fliagtcode"].ToString(), //航司
+                                                    startDate = atbc.Rows[j]["Day"].ToString(),        //整理起飞日期
+                                                    startTime = atbc.Rows[j]["StartTime"].ToString(),  //整理起飞时刻
+                                                    endDate = atbc.Rows[j]["ArrivedDate"].ToString(),  //整理抵达日期
+                                                    endTime = atbc.Rows[j]["EndTime"].ToString()       //整理抵达时刻
+                                                };
+                                                ListSSGDetails.Add(SSGDetails);
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        ListSSGDetails.Sort((a, b) => a.startDate.CompareTo(b.startDate));
+                        //得到并计算出入境时间
+                        if (ListSSGDetails != null)
+                        {
+                            string RuJingTime = ListSSGDetails.First().endDate + " " + ListSSGDetails.First().endTime.Substring(0, 2) + ":" + ListSSGDetails.First().endTime.Substring(2);  //入境时间
+                            string LiJingTime = ListSSGDetails[ListSSGDetails.Count - 1].startDate + " " + ListSSGDetails[ListSSGDetails.Count - 1].endTime.Substring(0, 2) + ":" + ListSSGDetails[ListSSGDetails.Count - 1].endTime.Substring(2); //离境时间
+
+                            stopDays = Convert.ToDateTime(LiJingTime).Subtract(Convert.ToDateTime(RuJingTime)).Days.ToString();
+                        }
+                    }
+                }
+
+                SetFieldValue(formFields, "MembeDesc", membeDescLabel);  //主要申根目的地 (以及其他申根目的地,如适用)
+                SetFieldValue(formFields, "FirstEntry", firstEntryLabel);  //首入申根国
+
+                //申请入境次数 
+                SetFieldValue(formFields, "27.SingleEntry", "");  //单次
+                SetFieldValue(formFields, "27.TwoEntries", "");  //两次
+                SetFieldValue(formFields, "27.MultipleEntries", "");  //多次
+                SetFieldValue(formFields, "27.IntendedDateIn", "");  //在申根地区预计首次停留的预计抵达日期
+                SetFieldValue(formFields, "27.IntendedDateOut", "");  //在申根地区预计首次停留之后的预计离开日期
+
+                //以往申根国签证是否有指纹记录
+                SetFieldValue(formFields, "28.No", "");  //否 
+                SetFieldValue(formFields, "28.Yes", "");  //是 
+                SetFieldValue(formFields, "28.KnownDate", "");  //如有,请写明日期
+                SetFieldValue(formFields, "28.KnownVisanumber", "");  //如有,请写明签证贴纸号码
+
+                //最后目的地之入境许可
+                SetFieldValue(formFields, "29.Issued", "");  //签发机关 
+                SetFieldValue(formFields, "29.ValidFrom", "");  //有效期由 
+                SetFieldValue(formFields, "29.Until", "");  //至 
+
+                //申根国的邀请人姓名。如无邀请人,请填写申根国的酒店或暂住居所名称
+                //得到当前国家下的城市酒店信息
+                var hotelInfo = _sqlSugar.Queryable<Grp_HotelReservations>()
+                    .Where(x => x.DiId == groupId)
+                    //.Where(x => x.City.contains(city))
+                    .First();
+                //酒店住址
+                if (hotelInfo != null)
+                {
+                    SetFieldValue(formFields, "30.SurnameFirstName", hotelInfo?.HotelName ?? "");  //申根国的酒店 
+                    var otherInfo = $"{hotelInfo.HotelAddress}";
+                    SetFieldValue(formFields, "30.HotelInfo", otherInfo);  //邀请人/酒店/暂住居所的地址及电子邮件 
+                    SetFieldValue(formFields, "30.TelephoneNo", hotelInfo?.HotelTel ?? "");  //电话号码
+                }
+
+                //邀请公司或机构的名称及地址
+                var oa = _sqlSugar.Queryable<Res_OfficialActivities>()
+                       .Where(x => x.IsDel == 0 && x.DiId == groupId)
+                       //.Where(x => x.Country.Contains(county))
+                       //.Where(x => x.Area.Contains(city))
+                       .First();
+                if (oa != null)
+                {
+                    //申根国的邀请人姓名。如无邀请人,请填写申根国的酒店或暂住居所名称
+                    SetFieldValue(formFields, "30.SurnameFirstName", oa?.Contact ?? "");  //申根国的酒店 
+                    SetFieldValue(formFields, "30.HotelInfo", oa?.Address ?? "");  //邀请人/酒店/暂住居所的地址及电子邮件 
+                    SetFieldValue(formFields, "30.TelephoneNo", oa?.Tel ?? "");  //电话号码
+
+                    string nameAddressLabel = $"{oa.Client} {oa.Address}";
+                    SetFieldValue(formFields, "31.NameAddress", nameAddressLabel);  //邀请公司或机构的名称及地址 
+                    string surnameLabel = $"{oa.Contact} {oa.Tel}";
+                    SetFieldValue(formFields, "31.Surname", surnameLabel);  //邀请公司或机构的联系人姓名、地址、电话号码及电子邮件 
+                    SetFieldValue(formFields, "31.TelephoneNo", oa?.Tel ?? "");  //邀请公司或机构的电话
+                }
+
+                //申请人的旅费以及在国外停留期间的生活费用
+                SetFieldValue(formFields, "32.Applicant", "");  //由申请人支付
+                SetFieldValue(formFields, "32.Cash", "");  //支付方式 - 现金
+                SetFieldValue(formFields, "32.Traveller", "");  //支付方式 - 旅行支票
+                SetFieldValue(formFields, "32.CreditCard", "");  //支付方式 - 信用卡
+                SetFieldValue(formFields, "32.Prepaid", "");  //支付方式 - 预缴住宿
+                SetFieldValue(formFields, "32.Prepaid1", "");  //支付方式 - 预缴交通
+                SetFieldValue(formFields, "32.OtherMeans", "");  //支付方式 - 其它
+                SetFieldValue(formFields, "32.OtherMeansDesc", "");  //支付方式 - 其它(请注明)
+
+                SetFieldValue(formFields, "32.Sponsor", "");  //由赞助方(邀请人、公司或机构)支付,请注明
+                SetFieldValue(formFields, "32.Referred", "");  //参见第 30 及 31 项
+                SetFieldValue(formFields, "32.ReferredDesc", "");  //参见第 30 及 31 项(请注明)
+                SetFieldValue(formFields, "32.OtherSponsor", "");  //其它
+                SetFieldValue(formFields, "32.OtherSponsorDesc", "");  //其它(请注明)
+
+                SetFieldValue(formFields, "32.SponsorCash", "");  //赞助方式 - 现金
+                SetFieldValue(formFields, "32.SponsorAccommodation", "");  //赞助方式 - 提供住宿
+                SetFieldValue(formFields, "32.SponsorExpenses", "");  //赞助方式 - 支付旅程期间所有开支
+                SetFieldValue(formFields, "32.SponsorPrepaid", "");  //赞助方式 - 预缴交通
+                SetFieldValue(formFields, "32.ponsorOther", "");  //赞助方式 - 其它
+                SetFieldValue(formFields, "32.ponsorOtherDesc", "");  //赞助方式 - 其它(请注明)
+
+                SetFieldValue(formFields, "33.PlaceAndDate", DateTime.Now.ToString("yyyy-MM-dd"));  //地点及日期
+                #endregion
+
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"生成PDF文件时出错: {ex.Message}", ex);
+            }
+            finally
+            {
+                // 按正确顺序关闭资源
+                stamper?.Close();
+                reader?.Close();
+                templateStream?.Close();
+                outputStream?.Close();
+            }
+
+            return outputPath;
+        }
+
         #endregion
 
         #region 文档下载辅助实体类
+
+        private class VisaDateView
+        {
+            /// <summary>
+            /// 团组信息
+            /// </summary>
+            public Grp_DelegationInfo groupInfo { get; set; }
+
+            /// <summary>
+            /// 身份证信息
+            /// </summary>
+            public Crm_CustomerCert idInfo { get; set; }
+
+            /// <summary>
+            /// 护照信息
+            /// </summary>
+            public Crm_CustomerCert passportInfo { get; set; }
+
+            /// <summary>
+            /// 客户信息
+            /// </summary>
+            public Crm_DeleClient custInfo { get; set; }
+
+            /// <summary>
+            /// 学校资料
+            /// </summary>
+            public List<Crm_VisaCustomerSchool> schoolInfos { get; set; }
+
+            /// <summary>
+            /// 家庭成员资料
+            /// </summary>
+            public List<Crm_VisaCustomerFamily> familyInfos { get; set; }
+
+            /// <summary>
+            /// 公司资料
+            /// </summary>
+            public List<Crm_VisaCustomerCompany> companyInfos { get; set; }
+        }
+
+        
         private class SSGDetails
         {
             public string Three { get; set; }        //三字码

+ 4 - 0
OASystem/OASystem.Api/appsettings.json

@@ -441,6 +441,10 @@
         {
           "Id": 21,
           "Name": "张海麟"
+        },
+        {
+          "Id": 281,
+          "Name": "伏虹瑾"
         }
       ]
     },