Преглед изворни кода

OP地接AI邮件与机票费用2026版重构支持

新增OP地接资料AI邮件相关接口(含SSE推送),完善appsettings配置及DTO。机票费用相关接口重构为2026版,支持分组、退票、舱位、客户等新需求,优化查询、保存、删除逻辑,提升数据一致性与扩展性。修复航班描述、客户名称等细节问题,增强代码结构和可维护性。
Lyyyi пре 1 недеља
родитељ
комит
ee85eadbba

+ 0 - 11
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -27290,7 +27290,6 @@ end as 'country'
 
             #endregion
 
-
             var groupCostMap = _mapper.Map<List<Grp_GroupCostDto>>(groupCost);
             var hotelNumber = _CostTypeHotelNumberRepository.GetCostTypeHotelNumberByDiid(diid); //酒店数量 可枚举
             var GroupCostParameter = _GroupCostParameterRepository.GetGroupCostParameterListByDiid(diid); //成本系数 可枚举
@@ -27675,7 +27674,6 @@ end as 'country'
                     }
                 }
 
-
                 #region 替换Word模板书签内容
 
                 Dictionary<string, string> marks = new Dictionary<string, string>();
@@ -29563,7 +29561,6 @@ end as 'country'
             return Ok(jw);
         }
 
-
         /// <summary>
         /// 根据黑屏代码Id重新生成行程
         /// </summary>
@@ -29599,7 +29596,6 @@ end as 'country'
             return Ok(jw);
         }
 
-
         /// <summary>
         /// 成本获取OP历史车费用
         /// </summary>
@@ -29616,9 +29612,6 @@ end as 'country'
                 //获取现有所有车的数据
                 if (!dto.Param.IsNullOrWhiteSpace())
                 {
-
-
-
                     string sql = $@"
 SELECT gdi.TeamName , gctggr.Area, gctggr.PriceName  , gctggrc.Id  ,gctggrc.Price , gctggrc.DatePrice , gctggrc.PriceContent , gctggrc.Currency , gctggr.ServiceStartTime , gctggr.ServiceEndTime 
 FROM  Grp_CarTouristGuideGroundReservationsContent gctggrc inner join Grp_CarTouristGuideGroundReservations gctggr  on 
@@ -29627,11 +29620,8 @@ WHERE gctggr.IsDel  = 0  and gctggrc.SId  = 91  AND  gdi.IsDel  = 0 AND  gctggrc
 AND  gctggr.ServiceEndTime is not NULL 
 ORDER by  gctggrc.id DESC 
 ";
-
                     dbResult = await _sqlSugar.SqlQueryable<HistoryCarDataView>(sql).ToListAsync();
 
-
-
                     var numeberResult = await Task.Run(() =>
                     {
                         var numberArr = dbResult.Where(x => int.TryParse(x.Area, out int number)).ToList();
@@ -29759,7 +29749,6 @@ ORDER by  gctggrc.id DESC
             return Ok(jw);
         }
 
-
         [HttpPost]
         public async Task<IActionResult> SearchGroupByKeyword(SearchGroupByKeywordDto dto)
         {

+ 422 - 1
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -9007,7 +9007,428 @@ WHERE
             }
         }
 
-        
+        /// <summary>
+        /// OP地接资料AI 邮件保存
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAIEmailSave(OpLocalServiceAIEmailSaveDto dto)
+        {
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择保存的团组"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (string.IsNullOrEmpty(dto.Guid)) return Ok(JsonView(false, "请传入Guid"));
+            if (string.IsNullOrEmpty(dto.EmailTitle)) return Ok(JsonView(false, "请传入邮件标题"));
+            if (string.IsNullOrEmpty(dto.EmailContent)) return Ok(JsonView(false, "请传入邮件内容"));
+
+            var opLocalAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>()
+                .Where(x => x.Id == dto.Id)
+                .FirstAsync();
+
+            if (opLocalAiInfo == null) return Ok(JsonView(false, "地接资料方信息为空"));
+
+            var dataList = opLocalAiInfo?.AiCrawledDetails;
+
+            if (dataList == null) return Ok(JsonView(false, "地接资料方信息为空"));
+
+            var editInfo = dataList.Where(x => x.Guid == dto.Guid).FirstOrDefault();
+
+            if (editInfo == null) return Ok(JsonView(false, "地接资料方信息为空"));
+
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+            editInfo.EmailInfo.EmailTitle = dto.EmailTitle;
+            editInfo.EmailInfo.EmailContent = dto.EmailContent;
+            editInfo.EmailInfo.Operator = opUserName;
+            editInfo.EmailInfo.OperatedAt = DateTime.Now;
+
+            var datas = dataList.Where(x => x.Guid != dto.Guid).ToList();
+            datas.Add(editInfo);
+            // 排序
+            opLocalAiInfo.AiCrawledDetails = datas.OrderByDescending(x => x.OperatedAt).ToList();
+
+            var editUpd = await _sqlSugar.Updateable(opLocalAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+            if (editUpd < 1)
+            {
+                return Ok(JsonView(true, "邮件保存失败"));
+            }
+
+            return Ok(JsonView(true, "邮件保存成功"));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 邮箱附件上传
+        /// </summary>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAIFileSave([FromForm] OpLocalServiceAIFileSaveDto dto)
+        {
+            // 1. 炼金前置:严格参数校验
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择保存的团组"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (string.IsNullOrEmpty(dto.Guid)) return Ok(JsonView(false, "请传入Guid"));
+            if (dto.Attachments == null || !dto.Attachments.Any()) return Ok(JsonView(false, "请传入附件"));
+
+            // 2. 数据获取:利用 SqlSugar 异步查询
+            var opAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>()
+                .InSingleAsync(dto.Id);
+
+            if (opAiInfo?.AiCrawledDetails == null) return Ok(JsonView(false, "地接方信息不存在"));
+
+            // 3. 业务定位:定位到具体需要编辑的 Guid 记录
+            var editInfo = opAiInfo.AiCrawledDetails.FirstOrDefault(x => x.Guid == dto.Guid);
+            if (editInfo == null) return Ok(JsonView(false, "未找到匹配的 Guid 记录"));
+
+            // 4. 追踪信息更新
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>()
+                .Where(x => x.Id == dto.CurrUserId && x.IsDel == 0)
+                .Select(x => x.CnName)
+                .FirstAsync() ?? "-";
+
+            // 更新操作人与时间 (利用引用类型特性)
+            editInfo.Operator = opUserName;
+            editInfo.OperatedAt = DateTime.Now;
+
+            // 初始化 EmailInfo 确保不为 null
+            editInfo.EmailInfo ??= new LocalEmailInfo();
+            editInfo.EmailInfo.Operator = opUserName;
+            editInfo.EmailInfo.OperatedAt = DateTime.Now;
+            editInfo.EmailInfo.AttachmentPaths ??= new List<string>();
+
+            // 5. 构建绝对路径与相对路径
+            string dirName = $"{editInfo.NameEn?.Trim() ?? "Default"}_{dto.Guid}";
+            string baseDir = AppSettingsHelper.Get("OpLocalServiceAIAssistBasePath");
+            string ftpBase = AppSettingsHelper.Get("OpLocalServiceAIAssistFtpPath");
+
+            string absolutePath = Path.Combine(baseDir, dirName);
+            string relativePathPrefix = $"{ftpBase}{dirName}/";
+
+            // 核心修复:确保父级目录递归创建,防止 DirectoryNotFoundException
+            if (!Directory.Exists(absolutePath))
+            {
+                Directory.CreateDirectory(absolutePath);
+            }
+
+            var newSavedRelativePaths = new List<string>();
+
+            // 6. 持续文件存储与实时验证
+            foreach (var file in dto.Attachments)
+            {
+                // 文件名安全过滤
+                string safeName = string.Join("_", file.FileName.Split(Path.GetInvalidFileNameChars()));
+                string fullPath = Path.Combine(absolutePath, safeName);
+
+                // 重名冲突处理:文件名(n).ext
+                if (System.IO.File.Exists(fullPath))
+                {
+                    string fileNameOnly = Path.GetFileNameWithoutExtension(safeName);
+                    string extension = Path.GetExtension(safeName);
+                    int count = 1;
+                    while (System.IO.File.Exists(fullPath))
+                    {
+                        safeName = $"{fileNameOnly}({count++}){extension}";
+                        fullPath = Path.Combine(absolutePath, safeName);
+                    }
+                }
+
+                try
+                {
+                    // 写入流:使用作用域隔离,确保退出 using 时立即释放句柄
+                    using (var stream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None))
+                    {
+                        await file.CopyToAsync(stream);
+                        await stream.FlushAsync(); // 强制落盘
+                    }
+
+                    // 实时验证:物理验证 + 逻辑校验
+                    var fileInfo = new FileInfo(fullPath);
+                    if (fileInfo.Exists && fileInfo.Length == file.Length)
+                    {
+                        newSavedRelativePaths.Add($"{relativePathPrefix}{safeName}");
+                    }
+                }
+                catch (Exception ex)
+                {
+                    // 发生 IO 异常时清理残余文件并抛出,触发外层处理
+                    if (System.IO.File.Exists(fullPath)) System.IO.File.Delete(fullPath);
+                    // 此处记录日志:
+                    _logger.LogError(ex, "File Save Error");
+                    continue; // 跳过失败的文件,继续下一个
+                }
+            }
+
+            if (!newSavedRelativePaths.Any()) return Ok(JsonView(false, "所有文件上传均失败"));
+
+            // 7. 数据合并与持久化:
+            // 将新路径与旧路径合并,并去重
+            var updatedPaths = editInfo.EmailInfo.AttachmentPaths;
+            updatedPaths.AddRange(newSavedRelativePaths);
+            editInfo.EmailInfo.AttachmentPaths = updatedPaths.Distinct().ToList();
+
+            // 重新排序整体列表
+            opAiInfo.AiCrawledDetails = opAiInfo.AiCrawledDetails
+                .OrderByDescending(x => x.OperatedAt)
+                .ToList();
+
+            // SqlSugar 高效部分列更新
+            var isSuccess = await _sqlSugar.Updateable(opAiInfo)
+                .UpdateColumns(x => x.AiCrawledDetails)
+                .ExecuteCommandAsync() > 0;
+
+            if (!isSuccess) return Ok(JsonView(false, "数据库更新失败"));
+
+            // 8. 返回前端数据 (支持 lowerCamelCase)
+            var officeBaseUrl = AppSettingsHelper.Get("OfficeBaseUrl");
+            var finalResultUrls = editInfo.EmailInfo.AttachmentPaths
+                .Select(path => $"{officeBaseUrl}{path}")
+                .ToList();
+
+            return Ok(JsonView(true, "邮件附件保存成功", finalResultUrls));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 邮箱附件删除
+        /// </summary>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> OpLocalServiceAIFileDel([FromBody] OpLocalServiceAIFileDelDto dto)
+        {
+            // 1. 基础校验
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择团组"));
+            if (string.IsNullOrEmpty(dto.Guid)) return Ok(JsonView(false, "请传入Guid"));
+            if (dto.AttachmentNames == null || !dto.AttachmentNames.Any()) return Ok(JsonView(false, "请传入待删除附件名称"));
+
+            // 2. 数据获取
+            var invAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().InSingleAsync(dto.Id);
+            if (invAiInfo?.AiCrawledDetails == null) return Ok(JsonView(false, "业务数据不存在"));
+
+            var editInfo = invAiInfo.AiCrawledDetails.FirstOrDefault(x => x.Guid == dto.Guid);
+            if (editInfo?.EmailInfo?.AttachmentPaths == null) return Ok(JsonView(false, "未找到对应的附件记录"));
+
+            // 3. 操作人追踪
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>()
+                .Where(x => x.Id == dto.CurrUserId && x.IsDel == 0)
+                .Select(x => x.CnName).FirstAsync() ?? "System";
+
+            // 更新追踪状态
+            editInfo.Operator = editInfo.EmailInfo.Operator = opUserName;
+            editInfo.OperatedAt = editInfo.EmailInfo.OperatedAt = DateTime.Now;
+
+            // 4. 路径
+            string dirName = $"{editInfo.NameEn?.Trim() ?? "Default"}_{dto.Guid}";
+            string absoluteDir = Path.Combine(AppSettingsHelper.Get("OpLocalServiceAIAssistBasePath"), dirName);
+
+            // 获取当前数据库中的相对路径列表
+            var currentPaths = editInfo.EmailInfo.AttachmentPaths;
+            bool isChanged = false;
+
+            // 5. 核心删除逻辑
+            foreach (var fileName in dto.AttachmentNames)
+            {
+                // 查找数据库中是否存在包含该文件名的路径 (忽略大小写比较)
+                var targetRelativePath = currentPaths.FirstOrDefault(p => p.EndsWith("/" + fileName) || p.Equals(fileName));
+
+                if (targetRelativePath != null)
+                {
+                    // A. 从数据库记录中移除
+                    currentPaths.Remove(targetRelativePath);
+                    isChanged = true;
+
+                    // B. 物理文件删除逻辑
+                    // 注意:fullPath 必须是 [基础路径] + [目录名] + [纯文件名]
+                    string fullPath = Path.Combine(absoluteDir, fileName);
+
+                    try
+                    {
+                        if (System.IO.File.Exists(fullPath))
+                        {
+                            System.IO.File.Delete(fullPath);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        // 记录 IO 异常但不中断流程
+                        _logger.LogWarning($"物理文件删除失败: {fullPath}, {ex.Message}");
+                    }
+                }
+            }
+
+            if (!isChanged) return Ok(JsonView(false, "未找到匹配的可删除附件"));
+
+            // 6. 数据同步与持久化
+            editInfo.EmailInfo.AttachmentPaths = currentPaths;
+
+            // 排序 (利用引用类型,无需手动 Add/Remove)
+            invAiInfo.AiCrawledDetails = invAiInfo.AiCrawledDetails.OrderByDescending(x => x.OperatedAt).ToList();
+
+            var isUpd = await _sqlSugar.Updateable(invAiInfo)
+                .UpdateColumns(x => x.AiCrawledDetails)
+                .ExecuteCommandAsync() > 0;
+
+            if (!isUpd) return Ok(JsonView(false, "数据库记录更新失败"));
+
+            // 7. 返回剩余附件的完整访问地址
+            var officeBaseUrl = AppSettingsHelper.Get("OfficeBaseUrl");
+            var remainingUrls = editInfo.EmailInfo.AttachmentPaths
+                .Select(path => $"{officeBaseUrl}{path}")
+                .ToList();
+
+            return Ok(JsonView(true, "附件删除成功", remainingUrls));
+        }
+
+        /// <summary>
+        /// OP地接资料AI 发送邮件(SSE 流式推送)
+        /// </summary>
+        [HttpPost]
+        public async Task OpLocalServiceAISeedEmailStream([FromBody] OpLocalServiceAISeedEmailStreamDto dto)
+        {
+            // 1. 初始化 SSE 
+            HttpContext.InitializeSse();
+
+            try
+            {
+                await HttpContext.SendSseStepAsync(5, "正在准备发送邮件队列...");
+
+                #region 1. 参数与权限前置校验
+                if (dto.Id < 1 || dto.CurrUserId < 1 || dto.Guids == null || !dto.Guids.Any())
+                {
+                    await HttpContext.SendSseStepAsync(-1, "参数验证失败,请检查选择的单位及用户状态。");
+                    return;
+                }
+
+                // hotmail 配置信息验证
+                var hotmailConfig = await _hotmailService.GetUserMailConfig(dto.CurrUserId);
+
+                (bool verify, string msg) = _hotmailService.ConfigVerify(hotmailConfig);
+                if (!verify)
+                {
+                    await HttpContext.SendSseStepAsync(-1, msg);
+                    return;
+                }
+
+                // 获取地接信息和用户信息
+                var opAiInfo = await _sqlSugar.Queryable<Res_OpLocalAI>().FirstAsync(x => x.IsDel == 0 && x.Id == dto.Id);
+                var userInfo = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId)
+                    .Select(x => new { x.Email, x.CnName })
+                    .FirstAsync();
+
+                if (opAiInfo?.AiCrawledDetails == null)
+                {
+                    await HttpContext.SendSseStepAsync(-1, "未找到有效的地接方数据。");
+                    return;
+                }
+
+                // 提取待发送的目标集合
+                var guidSet = new HashSet<string>(dto.Guids);
+                var seedInvInfos = opAiInfo.AiCrawledDetails.Where(x => guidSet.Contains(x.Guid)).ToList();
+
+                if (!seedInvInfos.Any())
+                {
+                    await HttpContext.SendSseStepAsync(-1, "所选单位信息在原始库中不存在。");
+                    return;
+                }
+
+                #endregion
+
+                await HttpContext.SendSseStepAsync(15, $"准备就绪,共计 {seedInvInfos.Count} 封邮件待发送...");
+
+                #region 2. 批量发送逻辑 (流式反馈)
+                int total = seedInvInfos.Count;
+                int current = 0;
+                var successCount = 0;
+                var failCount = 0;
+
+                foreach (var item in seedInvInfos)
+                {
+                    current++;
+                    // 计算进度:从 20% 到 90%
+                    int progress = 20 + (int)((double)current / total * 70);
+
+                    if (string.IsNullOrEmpty(item.EmailInfo?.EmailTitle) || string.IsNullOrEmpty(item.EmailInfo?.EmailContent))
+                    {
+                        await HttpContext.SendSseStepAsync(progress, $"跳过:{item.NameCn} (邮件标题或内容缺失)");
+                        failCount++;
+                        continue;
+                    }
+
+                    try
+                    {
+                        var req = new MailDto()
+                        {
+                            Subject = item.EmailInfo.EmailTitle,
+                            Content = item.EmailInfo.EmailContent,
+                            To = item.Email,
+                            AttachmentPaths = item.EmailInfo.AttachmentPaths
+                        };
+
+                        var res = await _hotmailService.SendMailAsync(hotmailConfig.UserName, req);
+
+                        if (res.IsSuccess)
+                        {
+                            successCount++;
+                            // 更新本地状态
+                            item.EmailInfo.Status = 4; // 发送成功状态
+                            item.EmailInfo.Operator = userInfo.CnName;
+                            item.EmailInfo.OperatedAt = DateTime.Now;
+
+                            await HttpContext.SendSseStepAsync(progress, $"成功:已向 {item.NameCn}({item.Email}) 发送邮件");
+                        }
+                        else
+                        {
+                            failCount++;
+                            await HttpContext.SendSseStepAsync(progress, $"失败:{item.NameCn} 发送失败 ({res.Message})");
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        failCount++;
+                        _logger.LogError(ex, $"企微邮件推送异常:{item.NameCn}");
+                        await HttpContext.SendSseStepAsync(progress, $"异常:{item.NameCn} 连接超时");
+                    }
+
+                }
+                #endregion
+
+                await HttpContext.SendSseStepAsync(95, "正在归档发送记录...");
+
+                #region 3. 数据同步与收尾
+                // 更新内存中的全量数据:排除旧的,加入已更新状态的
+                opAiInfo.AiCrawledDetails = opAiInfo.AiCrawledDetails
+                    .Where(x => !guidSet.Contains(x.Guid))
+                    .Concat(seedInvInfos)
+                    .OrderByDescending(x => x.EmailInfo.OperatedAt)
+                    .ToList();
+
+                // 精准更新 JSON 列
+                var updateOk = await _sqlSugar.Updateable(opAiInfo)
+                    .UpdateColumns(x => x.AiCrawledDetails)
+                    .ExecuteCommandHasChangeAsync();
+
+                if (!updateOk)
+                {
+                    await HttpContext.SendSseStepAsync(-1, "记录保存失败,请检查数据库连接。");
+                    return;
+                }
+
+                await HttpContext.SendSseStepAsync(100, $"任务完成!成功: {successCount}, 失败: {failCount}", new
+                {
+                    Id = opAiInfo.Id,
+                    SuccessCount = successCount,
+                    FailCount = failCount
+                });
+                #endregion
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "邮件发送流发生崩溃");
+                await HttpContext.SendSseStepAsync(-1, $"系统错误:{ex.Message}");
+            }
+            finally
+            {
+                await HttpContext.FinalizeSseAsync();
+            }
+        }
 
         #endregion
     }

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

@@ -173,6 +173,9 @@
   // 商邀资料 AI 文件路径配置
   "InvitationAIAssistBasePath": "D:/FTP/File/OA2023/Office/Word/InvitationAIAssist/",
   "InvitationAIAssistFtpPath": "Office/Word/InvitationAIAssist/",
+  // op地接资料 AI 文件配置路径
+  "OpLocalServiceAIAssistBasePath": "D:/FTP/File/OA2023/Office/Word/OpLocalServiceAIAssist/",
+  "OpLocalServiceAIAssistFtpPath": "Office/Word/OpLocalServiceAIAssist/",
   "CTableCorrelationPageDatas": [
     {
       "CTableId": 76, //CtableId 酒店预订

+ 53 - 1
OASystem/OASystem.Domain/Dtos/Resource/OpLocalServiceAIDto.cs

@@ -1,4 +1,5 @@
-using OASystem.Domain.Entities.Resource;
+using Microsoft.AspNetCore.Http;
+using OASystem.Domain.Entities.Resource;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -98,4 +99,55 @@ namespace OASystem.Domain.Dtos.Resource
     public class OpLocalServiceAIGenerateEmailStreamDto : OpLocalServiceAIGenerateEmailDto
     {
     }
+
+    public class OpLocalServiceAIEmailSaveDto : OpLocalServiceAISearchDto
+    {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string Guid { get; set; }
+
+        /// <summary>
+        /// 邮件标题
+        /// </summary>
+        public string EmailTitle { get; set; }
+
+        /// <summary>
+        /// 邮箱内容
+        /// </summary>
+        public string EmailContent { get; set; }
+    }
+
+    public class OpLocalServiceAIFileSaveDto : OpLocalServiceAISearchDto 
+    {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string Guid { get; set; }
+
+        /// <summary>
+        /// 附件
+        /// </summary>
+        public List<IFormFile> Attachments { get; set; }
+    }
+
+    public class OpLocalServiceAIFileDelDto : InvitationAISearchDto
+    {
+
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string Guid { get; set; }
+
+        /// <summary>
+        /// 附件Names
+        /// </summary>
+        public List<string> AttachmentNames { get; set; }
+    }
+
+    public class OpLocalServiceAISeedEmailStreamDto : InvitationAISearchDto
+    {
+        public List<string> Guids { get; set; }
+    }
+
 }

Разлика између датотеке није приказан због своје велике величине
+ 1619 - 1624
OASystem/OASystem.Infrastructure/Repositories/Groups/AirTicketResRepository.cs