using DG.Core; using Exceptionless.Models; using Hg.Complaint.Domain.Dto.ContentModel; using Hg.Core.Entity.Complaint; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.DependencyInjection; using MySqlConnector; using Oracle.ManagedDataAccess.Client; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Net.Http; using System.Reflection; using System.Security.Cryptography; using System.Text.Encodings.Web; using System.Text.Json; using System.Xml.Linq; namespace Hg.Complaint.Domain { internal class ComplaintDomain : IComplaintDomain { private readonly IServiceProvider _serviceProvider; private readonly ComplaintEventSingleton _complaintEventSingleton; private readonly IRedisManager _redisManager; private readonly IConfiguration _configuration; private readonly IHttpClient _httpClient; private readonly IMapper _mapper; private readonly ICacheDomain _cacheDomain; private readonly int _retryCount = 3; private readonly SystemConfig _systemConfig; public ComplaintDomain( ComplaintEventSingleton complaintEventSingleton, IRedisManager redisManager, IConfiguration configuration, IMapper mapper, IHttpClient httpClient, ICacheDomain cacheDomain, IServiceProvider serviceProvider) { _systemConfig = configuration.GetSection("SystemConfig").Get(); _complaintEventSingleton = complaintEventSingleton; _redisManager = redisManager; _mapper = mapper; _httpClient = httpClient; _configuration = configuration; _cacheDomain = cacheDomain; _serviceProvider = serviceProvider; } /// /// 分析消极消息 /// /// /// public async Task AnalyseNegativeMessage(NegativeMessageDto dto) { var applyComplaint = new ApplyComplaintDto { Appid = dto.Corpid, Appuserid = dto.EnternalAppuserid, Unionid = dto.ExternalUnionid, Deptid = dto.InternalDeptid, CrmAppid = "", Source = ComplaintSource.企微聊天记录, Resid = dto.ExternalResid, Content = dto.ToJson(), SignType = ComplaintSignType.违规关键词, SignWay = ComplaintSignWay.系统标记, }; return await ApplyComplaint(applyComplaint); } /// /// 申请投诉 /// /// /// public async Task ApplyComplaint(ApplyComplaintDto dto) { var key = CacheKeys.ComplaintMessageEnqueue; using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); Log.Information($"投诉申请日志:dto:{dto.ToJson()}"); if (dto.Channel.HasValue) { var deptments = await _cacheDomain.GetDeptments(); var deptment = deptments.FirstOrDefault(y => y.DeptmentCampains.Any(a => dto.Channel >= a.StartCampainId && dto.Channel <= a.EndCampainId)); if (deptment != null) { dto.Deptid = deptment.Id; } } var complaintLog = new ComplaintLog { Appid = dto.Appid, Appuserid = dto.Appuserid, Unionid = dto.Unionid, Deptid = dto.Deptid, CrmAppid = dto.CrmAppid, Source = dto.Source, Resid = dto.Resid, Content = dto.Content, Ctime = DateTime.Now, IsLast = false, SignType = dto.SignType, SignWay = dto.SignWay, IsCompletion = false, Channel = dto.Channel, Eid = dto.Eid, Ename = dto.Ename, Reason = dto.Reason, }; complaintLog = await hgActionRepository.GetRepository().InsertAsync(complaintLog); var messageDto = _mapper.Map(complaintLog); await _redisManager.EnqueueAsync(key, messageDto); return true; } /// /// 获取队列数量 /// /// public async Task GetQueue() { var key = CacheKeys.ComplaintMessageEnqueue; if (!await _redisManager.ExistsAsync(key)) { return 0; } return await _redisManager.CountAsync(key); } /// /// 消费单个队列对象 /// /// public async Task DequeueQueue() { var key = CacheKeys.ComplaintMessageEnqueue; if (!await _redisManager.ExistsAsync(key)) { return new ComplaintLogMessageDto(); } return await _redisManager.DequeueAsync(key); } /// /// 获取队列信息 /// /// public async Task> GetQueues() { var key = CacheKeys.ComplaintMessageEnqueue; if (!await _redisManager.ExistsAsync(key)) { return new List(); } return await _redisManager.GetListAsync(key); } /// /// 分析投诉日志 /// /// /// public async Task AnalyseComplaintLog(ComplaintLogMessageDto messageDto) { Log.Information($"开始分析投诉日志:dto:{messageDto.ToJson()}"); var key = CacheKeys.ComplaintMessageEnqueue; using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var complaintData = await GetComplaintUser(messageDto); var complaintUser = complaintData.Item1; var complaintUserDept = complaintData.Item2; if (complaintUser == null) { if (messageDto.RetryCount > _retryCount) { Log.Error($"用户数据获取失败,已重试超过{_retryCount}次!dto:{messageDto.ToJson()}"); return; } Log.Information($"用户数据获取失败,下次重试!dto:{messageDto.ToJson()}"); // 丢到新的用户队列 messageDto.RetryCount++; await _redisManager.EnqueueAsync(key, messageDto); return; } if (complaintUserDept == null) { if (messageDto.Deptid == 0) { messageDto.Udid = 0; } else { if (messageDto.RetryCount > _retryCount) { Log.Error($"事业部组不存,已重试超过{_retryCount}次!dto:{messageDto.ToJson()}"); return; } Log.Information($"事业部组不存,下次重试!dto:{messageDto.ToJson()}"); messageDto.RetryCount++; await _redisManager.EnqueueAsync(key, messageDto); return; } } else { messageDto.Udid = complaintUserDept.Id; } var complaintLog = _mapper.Map(messageDto); complaintLog.Fid = complaintUser.Id; complaintLog.IsLast = true; var oldLog = await hgActionRepository.GetRepository().Query() .Where(x => x.Udid == complaintLog.Udid && x.IsLast == true) .FirstOrDefaultAsync(); var transaction = await hgActionRepository.BeginTransactionAsync(); try { // 更新是否有订单字段 if (complaintUserDept != null && !complaintUserDept.HasOrder) { var hasOrder = await HasOrderByResid(complaintUser.Resid, complaintUserDept.Deptid); if (complaintUserDept.HasOrder != hasOrder) { complaintUserDept.HasOrder = hasOrder; await hgActionRepository.GetRepository().UpdateAsync(complaintUserDept, x => new { x.HasOrder }); } } if (complaintUserDept != null) { complaintUserDept.Status = ComplaintStatus.待跟进; await hgActionRepository.GetRepository().UpdateAsync(complaintUserDept, x => new { x.Status }); _complaintEventSingleton.AddComplaintEvent(new ComplaintEventDto { Id = complaintUserDept.Id, Fid = complaintUserDept.Fid, Ctime = complaintLog.Ctime }); } if (oldLog != null) { oldLog.IsLast = false; await hgActionRepository.GetRepository().UpdateAsync(oldLog, x => new { x.IsLast }); } complaintLog = await hgActionRepository.GetRepository().UpdateAsync(complaintLog, x => new { x.Fid, x.Udid, x.IsLast }); await transaction.CommitAsync(); } catch (Exception ex) { await transaction.RollbackAsync(); await transaction.DisposeAsync(); Log.Error(ex, $"分析投诉日志失败!dto:{messageDto.ToJson()}"); throw; } } /// /// 获取投诉用户和投诉用户归属 /// /// /// private async Task<(ComplaintUser?, ComplaintUserDept?)> GetComplaintUser(ComplaintLog complaintLog) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); Expression> expressionComplaintUser = x => 1 == 1; if (!string.IsNullOrEmpty(complaintLog.Appid) && !string.IsNullOrEmpty(complaintLog.Appuserid)) { expressionComplaintUser = expressionComplaintUser.And(x => x.Appid == complaintLog.Appid && x.Appuserid == complaintLog.Appuserid); } else if (!string.IsNullOrEmpty(complaintLog.Resid)) { expressionComplaintUser = expressionComplaintUser.And(x => x.Resid == complaintLog.Resid); } else if (!string.IsNullOrEmpty(complaintLog.Unionid)) { expressionComplaintUser = expressionComplaintUser.And(x => x.Unionid == complaintLog.Unionid); } var queryComplaintUser = hgActionRepository.GetRepository().Query() .Where(expressionComplaintUser); var complaintUserDept = new ComplaintUserDept(); var complaintUser = await queryComplaintUser.FirstOrDefaultAsync(); // 先判断是否有已存在的投诉用户 if (complaintUser != null) { complaintUserDept = await GetOrCreatComplaintUserDept(complaintUser, complaintLog); return (complaintUser, complaintUserDept); } else { // 获取用户中心的用户信息 var user = await GetUserInfo(complaintLog.Appid, complaintLog.Appuserid, complaintLog.Resid, complaintLog.Unionid); if (user.Item2 == null || user.Item2.Uid == 0) { Log.Information($"用户数据获取失败!dto:{complaintLog.ToJson()}"); return (null, null); } complaintUser = await hgActionRepository.GetRepository().Query() .Where(x => x.Appid + x.Appuserid == $"{user.Item2.Appid}{user.Item2.Appuserid}") .FirstOrDefaultAsync(); // 如果用户中心的用户信息存在,但是投诉用户不存在,则创建投诉用户 if (complaintUser == null) { return await CreateComplaintUser(user.Item2, complaintLog); } } // 如果用户中心的用户信息存在,投诉用户也存在,则更新投诉用户信息 complaintUserDept = await GetOrCreatComplaintUserDept(complaintUser, complaintLog); return (complaintUser, complaintUserDept); } /// /// 获取用户信息 /// /// /// /// /// /// private async Task<(int, UserInfo?)> GetUserInfo(string? appid, string? appuserid, string? resid, string? unionid) { var userInfo = new UserInfo(); var cid = 0; if (!string.IsNullOrEmpty(appid) && !string.IsNullOrEmpty(appuserid)) { // 从缓存中获取用户信息 var uid = await _redisManager.ExistsAsync($"{appid}_1_{appuserid}", "UserCenter") ? await _redisManager.GetAsync($"{appid}_1_{appuserid}", "UserCenter") : 0; Log.Information($"在缓存key:【{appid}_1_{appuserid}】中获取 uid:{uid}"); if (uid == 0) { uid = await _redisManager.ExistsAsync($"{appid}_{appuserid}", "UserCenter") ? await _redisManager.GetAsync($"{appid}_{appuserid}", "UserCenter") : 0; Log.Information($"在缓存key:【{appid}_{appuserid}】中获取 uid:{uid}"); } if (uid > 0 && await _redisManager.ExistsAsync($"user_{uid}", "UserCenter")) { Log.Information($"在缓存key:【user_{uid}】"); var userInfoDto = await _redisManager.GetHashAsync($"user_{uid}", "UserCenter"); Log.Information($"在缓存key:【user_{uid}】, 获取数据:{userInfoDto.ToJson()}"); userInfo.Appuserid = userInfoDto.Appuserid; userInfo.Appid = userInfoDto.Appid; userInfo.Customerid = !string.IsNullOrEmpty(userInfoDto.Customerid) ? int.Parse(userInfoDto.Customerid) : 0; cid = userInfo.Customerid; } // 如果uid不等于cid, 则根据cid获取用户信息 if (userInfo.Uid != cid || string.IsNullOrEmpty(userInfo.Resid)) { userInfo = await GetUserInfoByCid(cid); } if (userInfo != null && string.IsNullOrEmpty(userInfo.Resid)) { userInfo ??= await GetUserInfoBySqlCondition("customerid", cid); } } else if (!string.IsNullOrEmpty(resid)) { // 从缓存中获取用户信息 cid = await _redisManager.ExistsAsync(resid, "UserCenter") ? await _redisManager.GetAsync(resid, "UserCenter") : 0; if (cid > 0) { // 如果存cid,则根据cid获取用户信息 userInfo = await GetUserInfoByCid(cid); } // 如果缓存中没有用户信息,则从数据库中获取 userInfo ??= await GetUserInfoBySqlCondition("Resid", resid); } else if (!string.IsNullOrEmpty(unionid)) { // 如果缓存中没有用户信息,则从数据库中获取 userInfo = await GetUserInfoBySqlCondition("Unionid", unionid); } return (cid, userInfo); } /// /// 根据客户id获取客户信息 /// /// /// private async Task GetUserInfoByCid(int cid) { var userInfo = await _redisManager.GetHashAsync($"user_{cid}", "UserCenter"); if (userInfo != null && string.IsNullOrEmpty(userInfo.Resid)) { var result = await _redisManager.GetDatabase("UserCenter").SetMembersAsync($"cid_{cid}"); var userInfos = new List(); #region 反序列化处理 foreach (var item in result) { if (!item.HasValue) continue; try { var data = JsonSerializer.Deserialize(item, new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); if (data == null) continue; userInfos.Add(new UserInfoDto { Appid = data.Appid, Appuserid = data.Appuserid, Uid = data.Uid.ToString() }); } catch (Exception e) { Log.Error($"反序列化失败:{e.Message}"); } try { var data = JsonSerializer.Deserialize(item, new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); if (data == null) continue; userInfos.Add(new UserInfoDto { Appid = data.Appid, Appuserid = data.Appuserid, Uid = data.Uid }); } catch (Exception e) { Log.Error($"反序列化失败:{e.Message}"); } } #endregion 反序列化处理 Log.Information($"在缓存key:【cid_{cid}】, 获取数据:{userInfos.ToJson()} "); if (userInfos != null && userInfos.Any()) { foreach (var user in userInfos) { Log.Information($"在缓存key:【user_{user.Uid}】"); var u = await _redisManager.GetHashAsync($"user_{user.Uid}", "UserCenter"); Log.Information($"在缓存key:【user_{user.Uid}】, 获取数据:{u.ToJson()}"); userInfo.Resid = !string.IsNullOrEmpty(u.Resid) ? u.Resid : userInfo.Resid; Log.Information($"成功获取resid:{userInfo.Resid}"); if (!string.IsNullOrEmpty(userInfo.Resid)) break; } } } return userInfo; } /// /// 根据sql和条件获取客户信息 /// /// /// /// private async Task GetUserInfoBySqlCondition(string key, object value) { var userInfo = new UserInfo(); var sql = @"SELECT Uid, Appid, Appuserid, Customerid, Resid, Unionid FROM UserInfo "; if (string.IsNullOrEmpty(key) || value == null) return null; using (var scope = _serviceProvider.CreateAsyncScope()) { var userCenterRepository = scope.ServiceProvider.GetRequiredService>(); sql += $"WHERE {key} = @{key}"; var param = new List() { new MySqlParameter() { ParameterName=key, MySqlDbType = MySqlDbType.VarChar, Value=value } }; Log.Information($"sql: {sql}, param: {value}"); var userInfos = await userCenterRepository.ExecuteSqlToListAsync(sql, param.ToArray()); userInfo = userInfos.FirstOrDefault(x => x.Uid == x.Customerid); if (userInfo != null && string.IsNullOrEmpty(userInfo.Resid)) { userInfo.Resid = userInfos.FirstOrDefault(x => !string.IsNullOrEmpty(x.Resid))?.Resid; } } return userInfo; } /// /// 获取用户名 /// /// /// private async Task GetUserName(string? resid) { if (string.IsNullOrEmpty(resid)) return ""; using var scope = _serviceProvider.CreateAsyncScope(); var zxdRepository = scope.ServiceProvider.GetRequiredService>(); var orders = await zxdRepository.GetRepository().Query() .Where(x => x.RESID == resid) .Distinct() .ToListAsync(); if (orders == null) return ""; if (orders.Any(x => !string.IsNullOrEmpty(x.CNAME))) { return orders.First(x => !string.IsNullOrEmpty(x.CNAME)).CNAME ?? ""; } else if (orders.Any(x => !string.IsNullOrEmpty(x.SOFTUSERNAME))) { var softUserName = orders.First(x => !string.IsNullOrEmpty(x.SOFTUSERNAME)).SOFTUSERNAME; var riskinfoUrl = _systemConfig.GetRiskinfo(); var bf = "{\"uid\": \"" + softUserName + "\"}"; var hqr = BlowFish.Encode(bf); var para = new { hqr }; var res = await _httpClient.PostAsync(riskinfoUrl, para); if (res.Ret == 0 && res.Businesstype != "smallAmount") { return res.Name ?? ""; } } return ""; } /// /// 获取或创建用户事业部关系 /// /// /// /// private async Task GetOrCreatComplaintUserDept(ComplaintUser complaintUser, ComplaintLog complaintLog) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var complaintUserDept = await hgActionRepository.GetRepository().Query() .Where(x => x.Fid == complaintUser.Id) .Where(x => x.Deptid == complaintLog.Deptid) .FirstOrDefaultAsync(); if (complaintUserDept == null) { try { var deptmentGroup = await _cacheDomain.GetDeptmentGroupByDeptid(complaintLog.Deptid); if (deptmentGroup == null) { Log.Information($"事业部组不存!id:{complaintLog.Deptid}"); return complaintUserDept; } complaintUserDept = new ComplaintUserDept { Deptid = complaintLog.Deptid, Ctime = DateTime.Now, DeptGroupId = deptmentGroup.Id, CrmAppid = complaintLog.Appid, Fid = complaintUser.Id, LastContent = complaintLog.Content, LastContentId = complaintLog.Id, Status = ComplaintStatus.待跟进 }; complaintUserDept = await hgActionRepository.GetRepository().InsertAsync(complaintUserDept); } catch (Exception ex) { Log.Error(ex, $"创建用户事业部关系失败!complaintUser:{complaintUser.ToJson()}, complaintLog:{complaintLog.ToJson()}"); throw; } } return complaintUserDept; } /// /// 创建用户 /// /// /// /// private async Task<(ComplaintUser, ComplaintUserDept?)> CreateComplaintUser(UserInfo userInfo, ComplaintLog complaintLog) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var deptmentGroup = await _cacheDomain.GetDeptmentGroupByDeptid(complaintLog.Deptid); var complaintUser = new ComplaintUser { Appid = userInfo.Appid, Appuserid = userInfo.Appuserid, Ctime = DateTime.Now, LastType = complaintLog.Source.ToString(), Resid = complaintLog.Resid ?? userInfo.Resid, Unionid = userInfo.Unionid, Uname = await GetUserName(userInfo.Resid) }; var transaction = await hgActionRepository.BeginTransactionAsync(); try { complaintUser = await hgActionRepository.GetRepository().InsertAsync(complaintUser); if (deptmentGroup == null) { Log.Information($"事业部组不存!id:{complaintLog.Deptid}"); return (complaintUser, null); } else { var complaintUserDept = await hgActionRepository.GetRepository().Query() .Where(x => x.Fid == complaintUser.Id) .Where(x => x.Deptid == complaintLog.Deptid) .FirstOrDefaultAsync(); if (complaintUserDept == null) { complaintUserDept = new ComplaintUserDept { Deptid = complaintLog.Deptid, Ctime = DateTime.Now, DeptGroupId = deptmentGroup.Id, CrmAppid = complaintLog.Appid, Fid = complaintUser.Id, LastContent = complaintLog.Content, LastContentId = complaintLog.Id, Status = ComplaintStatus.待跟进 }; if (!string.IsNullOrEmpty(userInfo.Resid)) { complaintUserDept.HasOrder = await HasOrderByResid(complaintUser.Resid, complaintLog.Deptid); } complaintUserDept = await hgActionRepository.GetRepository().InsertAsync(complaintUserDept); } else { complaintUserDept.LastContent = complaintLog.Content; complaintUserDept.LastContentId = complaintLog.Id; complaintUserDept.Status = ComplaintStatus.待跟进; complaintUserDept.Utime = DateTime.Now; await hgActionRepository.GetRepository().UpdateAsync(complaintUserDept, x => new { x.LastContent, x.LastContentId, x.Status, x.Utime }); } await transaction.CommitAsync(); return (complaintUser, complaintUserDept); } } catch (Exception ex) { await transaction.RollbackAsync(); await transaction.DisposeAsync(); Log.Error(ex, $"创建用户失败!dto:{complaintLog.ToJson()}"); throw; } } /// /// 根据resid获取用户是否有订单 /// /// /// /// private async Task HasOrderByResid(string? resid, int? deptid) { var deptments = await _cacheDomain.GetDeptments(); var deptment = deptments.FirstOrDefault(y => y.Id == deptid); using var scope = _serviceProvider.CreateAsyncScope(); var zxdRepository = scope.ServiceProvider.GetRequiredService>(); var where = PredicateExtensionses.True(); where = where.And(x => x.RESID == resid); var whereOr = PredicateExtensionses.False(); if (deptment != null && deptment.DeptmentCampains != null && deptment.DeptmentCampains.Any()) { foreach (var item in deptment.DeptmentCampains) { whereOr = whereOr.Or(m => m.CHANNEL >= item.StartCampainId && m.CHANNEL <= item.EndCampainId); } } where = where.And(whereOr); return await zxdRepository.GetRepository().Query() .Where(where) .AnyAsync(); } private async Task GetData(string url, string appid) where T : class, new() { var client = new System.Net.Http.HttpClient(); client.DefaultRequestHeaders.Add("appid", appid); var result = await client.GetAsync(url); var bytes = await result.Content.ReadAsByteArrayAsync(); var json = Encoding.UTF8.GetString(bytes); var obj = Newtonsoft.Json.JsonConvert.DeserializeObject(json); return obj; } /// /// 分页 /// /// /// /// public async Task> GetPage(SearchComplaintDto dto, string? appid) { var deptmentGroups = await _cacheDomain.GetDeptmentGroups(); var deptments = await _cacheDomain.GetDeptments(); using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var zxdRepository = scope.ServiceProvider.GetRequiredService>(); var userQuery = hgActionRepository.GetRepository().Query(); var logQuery = hgActionRepository.GetRepository().Query(); var deptQuery = hgActionRepository.GetRepository().Query(); var followQuery = hgActionRepository.GetRepository().Query(); var cur = zxdRepository.GetRepository().Query(); var eids = new List(); var deptids = new List(); #region UMID转RESID if (!string.IsNullOrEmpty(dto.UMID) && string.IsNullOrEmpty(dto.ResId)) { var UMIDMain = cur.FirstOrDefault(m => m.UMID == dto.UMID); if (UMIDMain != null) { dto.ResId = UMIDMain.RESID; } else { dto.ResId = "NULL_RESID"; } } #endregion if (!string.IsNullOrEmpty(dto.Txt_deptId) || !string.IsNullOrEmpty(dto.Txt_groupIds)) { //接口需要appid传到header中,此方法不适用,暂时改为自定义方法 //var eidResult = await _httpClient.GetAsync>($"{_systemConfig.GetBusinessLineByDeptMentIds(dto.Txt_groupIds, dto.Txt_deptId)}", appid); var eidResult = await GetData>($"{_systemConfig.GetBusinessLineByDeptMentIds(dto.Txt_groupIds, dto.Txt_deptId)}", appid); if (eidResult.Code == 0) { if (eidResult.Data.IsLine) { dto.DeptId = eidResult.Data.DeptId.ToString(); } else { eids = eidResult.Data.EidInfo; } } else { Log.Error($"查询CRM组织结构接口报错, message: {eidResult.Message}"); } } if (dto.Txt_userId.HasValue) { //接口需要appid传到header中,此方法不适用,暂时改为自定义方法 //var usersResult = await _httpClient.GetAsync>>(_systemConfig.GetUserInfoByEIds(dto.Txt_userId.ToString()), appid); var usersResult = await GetData>>(_systemConfig.GetUserInfoByEIds(dto.Txt_userId.ToString()), appid); if (usersResult.Code == 0) { var userInfos = usersResult.Data; userInfos.ForEach(x => { if (x.Eid.HasValue) eids = new List { x.Eid.Value }; }); } else { Log.Error($"查询CRM组织结构接口报错, message: {usersResult.Message}"); } } if (!string.IsNullOrEmpty(appid)) { deptids = deptments.Where(x => x.Appid == appid && x.Id != 0).Select(x => x.Id).ToList(); } if (!string.IsNullOrWhiteSpace(dto.DeptId)) { deptids = dto.DeptId.Split(',').Select(n => Convert.ToInt32(n)).ToList(); } var endTime = dto.ETime.HasValue ? dto.ETime.Value.AddDays(1) : DateTime.Now; var query = from a in deptQuery join b in userQuery on a.Fid equals b.Id into tempB from b in tempB.DefaultIfEmpty() join c in logQuery.Where(x => x.IsLast) on a.Id equals c.Udid into tempC from c in tempC.DefaultIfEmpty() join d in followQuery.Where(x => x.IsLast) on a.Id equals d.Udid into tempD from d in tempD.DefaultIfEmpty() select new ComplaintDto { Id = a.Id, ResId = b.Resid, DeptId = a.Deptid, SignType = c.SignType, SignWay = c.SignWay, ContentJson = c.Content, Source = c.Source, Ctime = c.Ctime, Status = a.Status, FollowContent = d.Content, FollowTime = d.Ctime, Eid = b.Eid, EName = d.Ename, BelongEname = b.Ename, Appid = b.Appid, Appuserid = b.Appuserid, HasOrder = a.HasOrder, HasAssign = b.Eid != null, Uname = b.Uname, Reason = c.Reason }; query = query.If(dto.Status.HasValue, x => x.Where(q => q.Status == dto.Status)) .If(!string.IsNullOrEmpty(dto.DeptId), x => x.Where(q => deptids.Contains(q.DeptId.Value))) .If(!string.IsNullOrEmpty(dto.ResId), x => x.Where(q => q.ResId.Contains(dto.ResId))) .If(dto.Source.HasValue, x => x.Where(q => q.Source == dto.Source)) .If(dto.SignWay.HasValue, x => x.Where(q => q.SignWay == dto.SignWay)) .If(dto.SignType.HasValue, x => x.Where(q => q.SignType == dto.SignType)) .If(dto.STime.HasValue, x => x.Where(q => q.Ctime >= dto.STime)) .If(dto.ETime.HasValue, x => x.Where(q => q.Ctime < endTime)) .If(!string.IsNullOrEmpty(dto.UName), x => x.Where(q => q.Uname.Contains(dto.UName))) .If(eids.Count > 0, x => x.Where(x => eids.Contains(x.Eid.Value))) .If(dto.HasAssign.HasValue, x => x.Where(q => q.HasAssign == dto.HasAssign)) .If(dto.HasOrder.HasValue, x => x.Where(q => q.HasOrder == dto.HasOrder)) .If(!string.IsNullOrEmpty(dto.Content), x => x.Where(q => q.ContentJson.Contains(dto.Content))) .If(deptids != null && deptids.Any(), x => x.Where(x => deptids.Contains(x.DeptId.Value))); var total = await query.CountAsync(); var data = await query.OrderByDescending(x => x.Ctime) .Skip((dto.PageIndex - 1) * dto.PageSize) .Take(dto.PageSize) .ToListAsync(); var resids = data.Where(x => !string.IsNullOrEmpty(x.ResId)).Select(x => x.ResId).Distinct().ToList(); var mycur = cur.Where(m => resids.Contains(m.RESID)).ToList(); foreach (var item in data) { var deptmentGroup = deptmentGroups.FirstOrDefault(x => x.Deptments != null && x.Deptments.Any(y => y.Id == item.DeptId)); if (deptmentGroup != null && deptmentGroup.Deptments != null && deptmentGroup.Deptments.Any()) { item.DeptGroupName = deptmentGroup.GroupName; item.DeptName = deptmentGroup.Deptments.First(x => x.Id == item.DeptId).Title; } try { switch (item.Source) { case ComplaintSource.官网: case ComplaintSource.公众号投诉: case ComplaintSource.企微客服名片投诉: case ComplaintSource.软件app: { if (!string.IsNullOrEmpty(item.ContentJson)) { var model = JsonSerializer.Deserialize(item.ContentJson); item.Content = model?.Content; } break; } case ComplaintSource.企微聊天记录: { if (!string.IsNullOrEmpty(item.ContentJson)) { var model = JsonSerializer.Deserialize(item.ContentJson); item.Content = model?.TextContent; item.Keywords = model?.IllegalWords != null && model.IllegalWords.Any() ? string.Join(";", model?.IllegalWords) : ""; } break; } case ComplaintSource.标为水军: { item.Content = @$"
{item.Uname}: {item.ResId}
{item.Reason?.GetDescription()}: {item.ContentJson}
"; break; } default: item.Content = item.ContentJson; break; }; } catch (Exception ex) { Log.Error(ex, "投诉内容json解析失败!"); } item.UMID = mycur.FirstOrDefault(m => m.RESID == item.ResId)?.UMID; } return new PageResult(dto.PageIndex, dto.PageSize, total, data); } /// /// 重新发起队列 /// /// public async Task> SyncQueue() { var key = CacheKeys.ComplaintMessageEnqueue; using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var data = await hgActionRepository.GetRepository().Query() .Where(x => x.Udid == null && x.Fid == null) .ToListAsync(); await _redisManager.RemoveAsync(key); foreach (var item in data) { var messageDto = _mapper.Map(item); await _redisManager.EnqueueAsync(key, messageDto); } return _mapper.Map(data); } public async Task GetComplaintDetail(string? appid, string? appuserid, int? deptid) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var query = from a in hgActionRepository.GetRepository().Query() join b in hgActionRepository.GetRepository().Query() on a.Id equals b.Fid where a.Appid == appid && a.Appuserid == appuserid && b.Deptid == deptid select b.Id; var userDeptid = await query.FirstOrDefaultAsync(); if (userDeptid > 0) { return await GetComplaintDetail(userDeptid); } throw new ApiException("未找到投诉记录"); } /// /// 根据资源id和业务线获取投诉详情 /// /// /// /// public async Task GetComplaintDetail(string? resid, int? deptid) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var query = from a in hgActionRepository.GetRepository().Query() join b in hgActionRepository.GetRepository().Query() on a.Id equals b.Fid where a.Resid == resid && b.Deptid == deptid select b.Id; var userDeptid = await query.FirstOrDefaultAsync(); if (userDeptid > 0) { return await GetComplaintDetail(userDeptid); } throw new ApiException("未找到投诉记录"); } /// /// 获取投诉详情 /// /// /// /// public async Task GetComplaintDetail(int id) { var deptmentGroups = await _cacheDomain.GetDeptmentGroups(); using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var data = await (from a in hgActionRepository.GetRepository().Query() join b in hgActionRepository.GetRepository().Query() on a.Fid equals b.Id where a.Id == id select new ComplaintDetailDto { Id = a.Id, Fid = a.Fid, Status = a.Status, Deptid = a.Deptid, Uname = b.Uname, Resid = b.Resid }).FirstOrDefaultAsync() ?? throw new ApiException("未找到投诉记录"); data.ComplaintLogDetails = await hgActionRepository.GetRepository().Query() .Where(x => x.Udid == data.Id && x.Fid == data.Fid) .OrderByDescending(x => x.Ctime) .Select(x => new ComplaintLogDetailDto { ContentJson = x.Content, SignType = x.SignType, SignWay = x.SignWay, Source = x.Source, Ctime = x.Ctime, Type = x.Type, Appid = x.Appid, Appuserid = x.Appuserid, Id = x.Id, Deptid = x.Deptid, Eid = x.Eid, InternalNickname = x.Ename, Reason = x.Reason }).ToListAsync(); foreach (var item in data.ComplaintLogDetails) { if (item.Deptid != null) { var deptmentGroup = deptmentGroups.FirstOrDefault(x => x.Deptments != null && x.Deptments.Any(y => y.Id == item.Deptid)); if (deptmentGroup != null && deptmentGroup.Deptments != null && deptmentGroup.Deptments.Any()) { item.Deptname = deptmentGroup.Deptments.First(x => x.Id == item.Deptid).Title; } } switch (item.Source) { case ComplaintSource.官网: case ComplaintSource.公众号投诉: case ComplaintSource.企微客服名片投诉: case ComplaintSource.软件app: { if (!string.IsNullOrEmpty(item.ContentJson)) { var model = JsonHelper.FromJson(item.ContentJson); item.Content = model?.Content; } break; } case ComplaintSource.企微聊天记录: { if (!string.IsNullOrEmpty(item.ContentJson)) { var model = JsonHelper.FromJson(item.ContentJson); item.Nickname = model?.ExternalNickname; item.Content = model?.TextContent; item.InternalNickname = model?.InternalNickname; item.InternalAppuserid = model?.InternalAppuserid; item.Msgid = model?.Msgid; item.Keywords = model?.IllegalWords != null && model.IllegalWords.Any() ? string.Join(";", model?.IllegalWords) : ""; } break; } case ComplaintSource.标为水军: { item.Content = @$"
{data.Uname}: {data.Resid}
{item.Reason?.GetDescription()}: {item.ContentJson}
"; break; } default: item.Content = item.ContentJson; break; }; } data.ComplaintFollowDetails = await hgActionRepository.GetRepository().Query() .Where(x => x.Udid == data.Id && x.Fid == data.Fid) .OrderByDescending(x => x.Ctime) .Select(x => new ComplaintFollowDetailDto { Content = x.Content, Eid = x.Eid, Ename = x.Ename, Ctime = x.Ctime, Id = x.Id, Title = x.Title, Crmappid = x.Crmappid, }).ToListAsync(); if (data.ComplaintFollowDetails != null && data.ComplaintFollowDetails.Any()) { var appids = data.ComplaintFollowDetails.Where(x => !string.IsNullOrEmpty(x.Crmappid)).Select(x => x.Crmappid); foreach (var appid in appids) { var eids = string.Join(",", data.ComplaintFollowDetails.Where(x => x.Crmappid == appid).Select(x => x.Eid)); var usersResult = await _httpClient.GetAsync>>(_systemConfig.GetUserInfoByEIds(eids), appid); if (usersResult.Code == 0) { var userInfos = usersResult.Data; data.ComplaintFollowDetails.Where(x => x.Crmappid == appid).ToList().ForEach(x => { var userInfo = userInfos.FirstOrDefault(x => x.Eid == x.Eid); x.Deptname = userInfo?.DeptName; }); } } } return data; } /// /// 更新投诉状态 /// /// /// /// /// public async Task UpdateComplaintStatus(UpdateComplaintStatusDto dto, string? appid) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var data = await hgActionRepository.GetRepository().Query() .FirstOrDefaultAsync(x => x.Id == dto.Id) ?? throw new ApiException("未找到投诉记录"); var title = $"由【{data.Status.GetDescription()}】变更为【{dto.Status.GetDescription()}】"; if (data.Status == ComplaintStatus.已完结) { throw new ApiException($"投诉记录【{data.Status.GetDescription()}】,无法更新状态"); } if (data.Status > dto.Status && dto.Status != ComplaintStatus.待跟进已超时) { throw new ApiException($"投诉记录【{data.Status.GetDescription()}】,无法更新状态到【{dto.Status.GetDescription()}】"); } data.Status = dto.Status; data.Utime = DateTime.Now; var transaction = await hgActionRepository.BeginTransactionAsync(); try { var complaintLogs = await hgActionRepository.GetRepository().Query() .Where(x => x.Udid == data.Id && x.Fid == data.Fid && x.IsLast) .ToListAsync(); await hgActionRepository.GetRepository().UpdateAsync(data, x => new { x.Status, x.Utime }); var complaintUserFollows = await hgActionRepository.GetRepository().Query() .Where(x => x.Udid == data.Id && x.Fid == data.Fid && x.IsLast) .ToListAsync(); if (complaintUserFollows.Any()) { complaintUserFollows.ForEach(x => x.IsLast = false); await hgActionRepository.GetRepository().BatchUpdateAsync(complaintUserFollows, x => new { x.IsLast }); } var complaintUserFollow = new ComplaintUserFollow { Udid = data.Id, Fid = data.Fid, Content = dto.Content, Ctime = DateTime.Now, Eid = dto.Eid, Ename = dto.Ename, IsLast = true, Title = title, Crmappid = appid }; if (complaintLogs.Any()) { if (data.Status == ComplaintStatus.已完结) { complaintLogs.ForEach(x => x.IsCompletion = true); await hgActionRepository.GetRepository().BatchUpdateAsync(complaintLogs, x => new { x.IsCompletion }); } complaintUserFollow.LastContentId = complaintLogs.FirstOrDefault()?.Id; } await hgActionRepository.GetRepository().InsertAsync(complaintUserFollow); await transaction.CommitAsync(); if (dto.Status != ComplaintStatus.待跟进) { _complaintEventSingleton.RemoveComplaint(data.Id); } } catch (Exception ex) { Log.Error(ex, $"更新投诉状态报错!dto:{dto.ToJson()}"); } return true; } /// /// 获取投诉状态 /// /// /// public async Task GetComplaintStatus(int udid) { ComplainStatusModel res = new ComplainStatusModel(); using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var data = await hgActionRepository.GetRepository().Query() .FirstOrDefaultAsync(x => x.Id == udid); if (data != null) { res.Id = data.Id; res.Status = data.Status; } return res; } /// /// 标记客户 /// /// /// public async Task MarkCustomer(MarkCustomerDto dto) { try { ApplyComplaintDto applyComplaintDto = new ApplyComplaintDto(); if (dto.IsResid) { applyComplaintDto.Resid = dto.PostId; } else { applyComplaintDto.Unionid = dto.PostId; } applyComplaintDto.SignType = dto.Signtype; applyComplaintDto.SignWay = ComplaintSignWay.人工标记; applyComplaintDto.Source = dto.Type == 1 ? ComplaintSource.合规提交 : ComplaintSource.业务人员提交; applyComplaintDto.Content = dto.Content; applyComplaintDto.Eid = dto.Eid; applyComplaintDto.Ename = dto.Ename; if (!string.IsNullOrWhiteSpace(dto.DeptId)) { var depts = dto.DeptId.Split(",").ToList(); foreach (var dept in depts) { applyComplaintDto.Deptid = Convert.ToInt32(dept); await ApplyComplaint(applyComplaintDto); } } else { await ApplyComplaint(applyComplaintDto); } } catch (Exception ex) { Log.Error($"MarkCustomer{ex.Message}"); throw; } return true; } /// /// 初始化处理过期未处理投诉 /// /// public async Task InitOverdueComplaint() { var now = DateTime.Now; using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var dueTime = now.AddHours(-DefaultHelper.DueHours); var query = from a in hgActionRepository.GetRepository().Query() join b in hgActionRepository.GetRepository().Query() on a.Id equals b.Udid where a.Status == ComplaintStatus.待跟进 && b.IsLast select new ComplaintEventDto { Id = a.Id, Ctime = b.Ctime, Fid = a.Fid, }; var data = await query.ToListAsync(); if (data.Any()) { // 超时未跟进处理 var overdueData = data.Where(x => x.Ctime < dueTime).ToList(); foreach (var item in overdueData) { Log.Information($"系统处理超时未跟进,id: {item.Id}"); var dto = new UpdateComplaintStatusDto { Id = item.Id, Status = ComplaintStatus.待跟进已超时, Content = "超时未跟进", Ename = "系统" }; await UpdateComplaintStatus(dto, ""); } // 未超时未跟进处理 var dueData = data.Where(x => x.Ctime >= dueTime).ToList(); if (dueData.Any()) { _complaintEventSingleton.InitComplaintEvents(dueData); } } } /// /// 处理过期未处理投诉 /// /// public async Task OverdueComplaint() { var now = DateTime.Now; var dueTime = now.AddHours(-DefaultHelper.DueHours); var data = _complaintEventSingleton.GetComplaintEvents(); if (data.Any()) { var overdueData = data.Where(x => x.Ctime < dueTime).ToList(); foreach (var item in overdueData) { Log.Information($"系统处理超时未跟进,id: {item.Id}"); var dto = new UpdateComplaintStatusDto { Id = item.Id, Status = ComplaintStatus.待跟进已超时, Content = "超时未跟进", Ename = "系统" }; await UpdateComplaintStatus(dto, ""); } } } /// /// 刷新resid /// /// public async Task SyncResid() { //var json = "{\"Msgid\":\"12603565224196055910_1684218213018_external\",\"Msgtype\":\"text\",\"Recvtime\":null,\"Msgtime\":1684218208766,\"Corpid\":\"wwd4cd11d60db47118\",\"Corpname\":null,\"Tolist\":\"懂牛股票通\",\"Roomid\":\"\",\"Seq\":674415358,\"send_type\":2,\"text_content\":\"大盘调整从来就不是一件可怕的事,可怕的是咱们一直在做涨了去追,跌了割肉,迟迟不敢下手的事情!uD83DuDD25“大科技行情”可能即将出现起爆点!今年的半导体板块走出了一波行情, 但目前又回调到前期低点位置。 机会大于风险 - 值得我们持续关注, 要珍惜眼下黄金坑主线方向持续关注大科技与中特估一带一路, 大科技包括AI + 半导体 + 跨境支付 + 消费电子等等。 每个时期的主线机会都十分稀缺, 我们可以看到北上资金持续流入, 也预示了当前市场并不存在大的风险, 短期的回调都是为了更好的上涨!主力的每一次洗盘, 都是希望散户交出带血的筹码。 但跟对主线非常重要, 否则只剩下煎熬。 未来的市场普遍只存在局部牛市, 需要时刻紧盯主力动向才能享受到财富喜悦。( 投顾: 罗啼明; 执业编号: A0100622030001; 股市有风险, 投资需谨慎。)没有对的逻辑和正确的思维, 再好的低位也把握不住! 四月份的回调, 正是给五月份蓄能的契机。紧跟头部动向, 速度跟上咱们这轮的部署计划好吗? \",\"illegal_words \":[\"坑\"],\"external_resid \":\"257029289502501233\",\"external_unionid \":\"o5bj2wX--v3UdvKYz - L0oHY24aEw\",\"external_appuserid \":\"wmir6CCwAAfwgRKIz3oGLpK1Aus_YRvw\",\"external_nickname \":\"17709026787\",\"internal_eid \":802021,\"internal_groupid \":87,\"internal_deptid \":40,\"internal_employee_name \":\"陈景固\",\"internal_group_name \":\"客户服务二组 \",\"internal_dept_name \":\"六合投研服务中心 \",\"internal_unionid \":\"o5bj2wfT3_EPTluIfOq7q2gohlmU\",\"internal_nickname \":\"东高投研-陈经理\",\"internal_appuserid \":\"220321-134803-45\"}"; //var da = JsonHelper.FromJson(json); //var da2 = JsonSerializer.Deserialize(json); //var us = await GetUserInfo("wwd4cd11d60db47118", "221213-142659-33", "", ""); using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var query = from a in hgActionRepository.GetRepository().Query() join b in hgActionRepository.GetRepository().Query() on a.Fid equals b.Id where !string.IsNullOrEmpty(b.Resid) select new { a, b }; var data = await query.ToListAsync(); if (data.Any()) { foreach (var item in data) { try { // 获取用户中心的用户信息 //var user = await GetUserInfo(item.Appid, item.Appuserid, item.Resid, item.Unionid); var hasOrder = await HasOrderByResid(item.b.Resid, item.a.Deptid); if (hasOrder) { item.a.HasOrder = true; await hgActionRepository.GetRepository().UpdateAsync(item.a, x => new { x.HasOrder }); } } catch (Exception ex) { Log.Error(ex, $"刷新resid出错!data: {item.ToJson()}"); } } } } /// /// 批量分配客户给客服 /// /// /// /// public async Task BatchAssignComplaint(BatchAssignComplaintDto dto, string? appid) { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var complaintUsers = await (from a in hgActionRepository.GetRepository().Query() join b in hgActionRepository.GetRepository().Query() on a.Fid equals b.Id into abtemp from b in abtemp.DefaultIfEmpty() where dto.Ids.Contains(a.Id) select b).ToListAsync(); if (complaintUsers == null || !complaintUsers.Any()) throw new ApiException("投诉用户信息不存在!"); //接口需要appid传到header中,此方法不适用,暂时改为自定义方法 //var usersResult = await _httpClient.GetAsync>>(_systemConfig.GetUserInfoByEIds(dto.Eid.ToString()), appid); var usersResult = await GetData>>(_systemConfig.GetUserInfoByEIds(dto.Eid.ToString()), appid); var ename = ""; if (usersResult.Code == 0) { var userInfos = usersResult.Data; var userInfo = userInfos.FirstOrDefault(x => x.Eid == x.Eid); ename = userInfo?.Uname; } foreach (var complaintUser in complaintUsers) { complaintUser.Eid = dto.Eid; complaintUser.Ename = ename; complaintUser.Utime = DateTime.Now; } await hgActionRepository.GetRepository().BatchUpdateAsync(complaintUsers, x => new { x.Ename, x.Eid, x.Utime }); return true; } public async Task Total() { using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var querlog = hgActionRepository.GetRepository().Query().Select(x => new { x.Deptid, x.Fid, x.Udid, x.Source }).Distinct(); var query = hgActionRepository.GetRepository().Query() .GroupBy(x => x.Deptid) .Select(x => new { x.Key, 系统标记 = querlog.Where(a => a.Deptid == x.Key && a.Source == ComplaintSource.企微聊天记录) .Sum(x => 1), 客户投诉 = querlog.Where(a => a.Deptid == x.Key && new List { ComplaintSource.官网, ComplaintSource.PC软件, ComplaintSource.公众号投诉, ComplaintSource.软件app, ComplaintSource.企微客服名片投诉 }.Contains(a.Source.Value)).Sum(x => 1), 人工标记 = querlog.Where(a => a.Deptid == x.Key && new List { ComplaintSource.合规提交, ComplaintSource.业务人员提交 }.Contains(a.Source.Value)).Sum(x => 1), 总计 = x.Sum(x => 1), 待跟进 = x.Where(x => x.Status == ComplaintStatus.待跟进).Sum(x => 1), 比例1 = x.Where(x => x.Status == ComplaintStatus.待跟进).Sum(x => 1) / x.Sum(x => 1), 跟进中 = x.Where(x => x.Status == ComplaintStatus.跟进中).Sum(x => 1), 比例2 = x.Where(x => x.Status == ComplaintStatus.跟进中).Sum(x => 1) / x.Sum(x => 1), 已完结 = x.Where(x => x.Status == ComplaintStatus.已完结).Sum(x => 1), 比例3 = x.Where(x => x.Status == ComplaintStatus.已完结).Sum(x => 1) / x.Sum(x => 1), 已超时 = x.Where(x => x.Status == ComplaintStatus.待跟进已超时).Sum(x => 1), 比例4 = x.Where(x => x.Status == ComplaintStatus.待跟进已超时).Sum(x => 1) / x.Sum(x => 1), }); var data = await query.ToListAsync(); } public async Task MarkTrolls(MarkTrollsDto dto) { var deptids = dto.Deptid?.Split(',').Select(x => int.Parse(x)); if (deptids == null) return false; foreach (var deptid in deptids) { var applyComplaint = new ApplyComplaintDto { Deptid = deptid, Appid = dto.Appid, Appuserid = dto.Appuserid, Content = dto.Content, Eid = dto.Eid, SignWay = ComplaintSignWay.人工标记, Source = ComplaintSource.标为水军, Reason = dto.Reason }; await ApplyComplaint(applyComplaint); } return true; } public async Task ComplaintLabel(string resId) { if (string.IsNullOrWhiteSpace(resId)) throw new ApiException("请输入客户ID"); using var scope = _serviceProvider.CreateAsyncScope(); var hgActionRepository = scope.ServiceProvider.GetRequiredService>(); var id = await hgActionRepository.GetRepository().Query() .Where(x => x.Resid == resId).Select(x => x.Id).FirstOrDefaultAsync(); return id; } } }