SearchController.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. using OASystem.API.OAMethodLib;
  2. using OASystem.API.OAMethodLib.GenericSearch;
  3. using OASystem.Domain.AesEncryption;
  4. using OASystem.Domain.Entities.Customer;
  5. using OASystem.Domain.Entities.Groups;
  6. using OASystem.Domain.ViewModels.Search;
  7. using OASystem.Infrastructure.Repositories.CRM;
  8. using OASystem.Infrastructure.Repositories.Groups;
  9. using System.Diagnostics;
  10. namespace OASystem.API.Controllers
  11. {
  12. /// <summary>
  13. /// 搜索
  14. /// </summary>
  15. [Route("api/search")]
  16. [ApiController]
  17. public class SearchController : ControllerBase
  18. {
  19. private readonly SqlSugarClient _sqlSugar;
  20. private readonly DelegationInfoRepository _groupRep;
  21. private readonly DynamicSearchService<Grp_DelegationInfo> _groupSearchService;
  22. private readonly DynamicSearchService<NewClientDataView> _clientSearchService;
  23. private readonly DynamicSearchService<InvitationAIInvNameView> _invAISearchService;
  24. private readonly DynamicSearchService<AirInfo> _airSearchService;
  25. private readonly NewClientDataRepository _clientDataRepository;
  26. public SearchController(
  27. SqlSugarClient sqlSugar,
  28. DelegationInfoRepository groupRep,
  29. DynamicSearchService<Grp_DelegationInfo> groupSearchService,
  30. DynamicSearchService<NewClientDataView> clientSearchService,
  31. DynamicSearchService<InvitationAIInvNameView> invAISearchService,
  32. DynamicSearchService<AirInfo> airSearchService,
  33. NewClientDataRepository clientDataRepository
  34. )
  35. {
  36. _sqlSugar = sqlSugar;
  37. _groupRep = groupRep;
  38. _clientDataRepository = clientDataRepository;
  39. _groupSearchService = groupSearchService;
  40. _clientSearchService = clientSearchService;
  41. _airSearchService = airSearchService;
  42. _invAISearchService = invAISearchService;
  43. }
  44. /// <summary>
  45. /// 接团信息 关键字输入提示(单字段)
  46. /// </summary>
  47. /// <param name="keyword">关键字</param>
  48. /// <returns></returns>
  49. [HttpGet("group/{keyword}")]
  50. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  51. public async Task<IActionResult> GroupKeywordSearch(string keyword)
  52. {
  53. try
  54. {
  55. // 验证请求参数
  56. if (string.IsNullOrEmpty(keyword))
  57. {
  58. return Ok(JsonView(true, $"暂无数据!"));
  59. }
  60. var searchRequest = new DynamicSearchRequest
  61. {
  62. Keyword = keyword,
  63. RequireAllSingleChars = true,
  64. PageIndex = 1,
  65. PageSize = 999999,
  66. FieldWeights = new Dictionary<string, int>
  67. {
  68. { "TeamName", 10 },
  69. //{ "ClientUnit", 8 },
  70. //{ "ClientName", 6 }
  71. },
  72. Filters = new List<SearchFilter>()
  73. {
  74. new(){Field = "IsDel",Operator="eq",Value="0" }
  75. },
  76. OrderBy = "TeamName",
  77. ReturnFields = new List<string>() { "TeamName" }
  78. };
  79. // 验证字段配置
  80. var validation = _groupSearchService.ValidateFieldConfig(
  81. searchRequest.FieldWeights,
  82. searchRequest.ReturnFields);
  83. if (!validation.IsValid)
  84. {
  85. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  86. }
  87. var result = await _groupSearchService.SearchAsync(searchRequest);
  88. if (result.Success)
  89. {
  90. var data = result.Items.Select(x => new { x.Data.Id, x.Data.TeamName }).ToList();
  91. return Ok(JsonView(true, result.Message, data, data.Count));
  92. }
  93. return Ok(JsonView(true, result.Message));
  94. }
  95. catch (Exception ex)
  96. {
  97. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  98. }
  99. }
  100. /// <summary>
  101. /// 客户资料 关键字输入提示(多字段)
  102. /// </summary>
  103. /// <param name="userId">关键字</param>
  104. /// <param name="keyword">关键字</param>
  105. /// <returns></returns>
  106. [HttpGet("group/{userId}/{keyword}")]
  107. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  108. public async Task<IActionResult> ClientKeywordSearch(int userId, string keyword)
  109. {
  110. try
  111. {
  112. // 验证请求参数
  113. if (string.IsNullOrEmpty(keyword))
  114. {
  115. return Ok(JsonView(true, $"暂无数据!"));
  116. }
  117. //查询有权限的数据
  118. var clientIds = await _sqlSugar
  119. .Queryable<Crm_ClientDataAndUser>()
  120. .Where(x => x.IsDel == 0 && x.usersId == userId)
  121. .Select(x => x.NewClientDataId)
  122. .ToListAsync();
  123. if (clientIds == null || clientIds.Count < 1)
  124. {
  125. return Ok(JsonView(true, $"暂无数据!"));
  126. }
  127. var data = _sqlSugar.Queryable<Crm_NewClientData>()
  128. .Where(x => x.IsDel == 0 && clientIds.Contains(x.Id))
  129. .OrderByDescending(x => x.CreateTime)
  130. .Select(x => new NewClientDataView()
  131. {
  132. Id = x.Id,
  133. Client = x.Client,
  134. Contact = x.Contact,
  135. Job = x.Job,
  136. Telephone = x.Telephone,
  137. Location = x.Location,
  138. })
  139. .ToList();
  140. int index = 0;
  141. data.ForEach(x =>
  142. {
  143. index++;
  144. x.RowNumber = index;
  145. x.Client = AesEncryptionHelper.Decrypt(x.Client);
  146. x.Contact = AesEncryptionHelper.Decrypt(x.Contact);
  147. x.Job = AesEncryptionHelper.Decrypt(x.Job);
  148. x.Telephone = AesEncryptionHelper.Decrypt(x.Telephone);
  149. x.Location = AesEncryptionHelper.Decrypt(x.Location);
  150. });
  151. var searchRequest = new DynamicSearchRequest
  152. {
  153. Keyword = keyword,
  154. RequireAllSingleChars = true,
  155. PageIndex = 1,
  156. PageSize = 999999,
  157. FieldWeights = new Dictionary<string, int>
  158. {
  159. { "Client", 10 },
  160. { "Contact", 8 },
  161. { "Location", 6 }
  162. },
  163. OrderBy = "CreateTime"
  164. };
  165. // 验证字段配置
  166. var validation = _clientSearchService.ValidateFieldConfig(
  167. searchRequest.FieldWeights,
  168. searchRequest.ReturnFields);
  169. if (!validation.IsValid)
  170. {
  171. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  172. }
  173. var result = _clientSearchService.SearchDataSource(searchRequest, data);
  174. if (result.Success)
  175. {
  176. var view = result.Items.Select(x => x.Data).ToList();
  177. int resetIndex = 0;
  178. view.ForEach(x =>
  179. {
  180. resetIndex++;
  181. x.RowNumber = resetIndex;
  182. });
  183. return Ok(JsonView(true, result.Message, view, view.Count));
  184. }
  185. return Ok(JsonView(true, "暂无数据"));
  186. }
  187. catch (Exception ex)
  188. {
  189. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  190. }
  191. }
  192. /// <summary>
  193. /// 客户资料
  194. /// </summary>
  195. /// <param name="userId">关键字</param>
  196. /// <param name="keyword">关键字</param>
  197. /// <returns></returns>
  198. [HttpGet("ClientKeywordSearch/{userId}/{keyword}")]
  199. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  200. public async Task<IActionResult> ClientKeywordSearchAll(int userId, string keyword)
  201. {
  202. try
  203. {
  204. // 验证请求参数
  205. if (string.IsNullOrEmpty(keyword))
  206. {
  207. return Ok(JsonView(true, $"暂无数据!"));
  208. }
  209. var userIds = _clientDataRepository.GetNewExistClient(userId).Select(x => x.Id).ToList();
  210. //查询有权限的数据(包括负责用户)
  211. var clientIds = await _sqlSugar
  212. .Queryable<Crm_ClientDataAndUser>()
  213. .Where(x => x.IsDel == 0 && userIds.Contains(x.usersId))
  214. .Select(x => x.NewClientDataId)
  215. .ToListAsync();
  216. if (clientIds == null || clientIds.Count < 1)
  217. {
  218. return Ok(JsonView(true, $"暂无数据!"));
  219. }
  220. var data = _sqlSugar.Queryable<Crm_NewClientData>()
  221. .Where(x => x.IsDel == 0 && clientIds.Contains(x.Id))
  222. .OrderByDescending(x => x.CreateTime)
  223. .Select(x => new NewClientDataView()
  224. {
  225. Id = x.Id,
  226. Client = x.Client,
  227. Contact = x.Contact,
  228. Job = x.Job,
  229. Telephone = x.Telephone,
  230. Location = x.Location,
  231. })
  232. .ToList();
  233. int index = 0;
  234. data.ForEach(x =>
  235. {
  236. index++;
  237. x.RowNumber = index;
  238. x.Client = AesEncryptionHelper.Decrypt(x.Client);
  239. x.Contact = AesEncryptionHelper.Decrypt(x.Contact);
  240. x.Job = AesEncryptionHelper.Decrypt(x.Job);
  241. x.Telephone = AesEncryptionHelper.Decrypt(x.Telephone);
  242. x.Location = AesEncryptionHelper.Decrypt(x.Location);
  243. });
  244. var searchRequest = new DynamicSearchRequest
  245. {
  246. Keyword = keyword,
  247. RequireAllSingleChars = true,
  248. PageIndex = 1,
  249. PageSize = 999999,
  250. FieldWeights = new Dictionary<string, int>
  251. {
  252. { "Client", 10 },
  253. { "Contact", 8 },
  254. { "Location", 6 }
  255. },
  256. OrderBy = "CreateTime"
  257. };
  258. // 验证字段配置
  259. var validation = _clientSearchService.ValidateFieldConfig(
  260. searchRequest.FieldWeights,
  261. searchRequest.ReturnFields);
  262. if (!validation.IsValid)
  263. {
  264. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  265. }
  266. var result = _clientSearchService.SearchDataSource(searchRequest, data);
  267. if (result.Success)
  268. {
  269. var view = result.Items.Select(x => x.Data).ToList();
  270. int resetIndex = 0;
  271. view.ForEach(x =>
  272. {
  273. resetIndex++;
  274. x.RowNumber = resetIndex;
  275. });
  276. return Ok(JsonView(true, result.Message, view, view.Count));
  277. }
  278. return Ok(JsonView(true, "暂无数据"));
  279. }
  280. catch (Exception ex)
  281. {
  282. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  283. }
  284. }
  285. /// <summary>
  286. /// 团组、会务流程 关键字输入提示(智能版)
  287. /// </summary>
  288. /// <param name="keyword">关键字</param>
  289. /// <returns></returns>
  290. [HttpGet]
  291. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  292. public async Task<IActionResult> ProcessKeywordSearch(string keyword)
  293. {
  294. try
  295. {
  296. // 验证请求参数
  297. if (string.IsNullOrEmpty(keyword))
  298. {
  299. return Ok(JsonView(true, $"暂无数据!"));
  300. }
  301. var searchRequest = new DynamicSearchRequest
  302. {
  303. Keyword = keyword,
  304. RequireAllSingleChars = true,
  305. PageIndex = 1,
  306. PageSize = 999999,
  307. FieldWeights = new Dictionary<string, int>
  308. {
  309. { "TeamName", 10 }
  310. },
  311. Filters = new List<SearchFilter>()
  312. {
  313. new(){Field = "IsDel",Operator="eq",Value="0" }
  314. },
  315. OrderBy = "VisitDate",
  316. ReturnFields = new List<string>() { "TeamName" }
  317. };
  318. // 验证字段配置
  319. var validation = _groupSearchService.ValidateFieldConfig(
  320. searchRequest.FieldWeights,
  321. searchRequest.ReturnFields);
  322. if (!validation.IsValid)
  323. {
  324. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  325. }
  326. var result = await _groupSearchService.SearchAsync(searchRequest);
  327. if (result.Success)
  328. {
  329. var data = new List<dynamic>();
  330. foreach (var item in result.Items)
  331. {
  332. data.Add(new
  333. {
  334. item.Data.Id,
  335. item.Data.TeamName,
  336. });
  337. }
  338. return Ok(JsonView(true, result.Message, data, data.Count));
  339. }
  340. return Ok(JsonView(true, result.Message));
  341. }
  342. catch (Exception ex)
  343. {
  344. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  345. }
  346. }
  347. /// <summary>
  348. /// 团组会务成本 关键字输入提示(智能版)
  349. /// </summary>
  350. /// <param name="keyword">关键字</param>
  351. /// <returns></returns>
  352. [HttpGet("ConferenceAffairsKeywordSearch/{keyword}")]
  353. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  354. public async Task<IActionResult> ConferenceAffairsKeywordSearch(string keyword)
  355. {
  356. try
  357. {
  358. // 验证请求参数
  359. if (string.IsNullOrEmpty(keyword))
  360. {
  361. return Ok(JsonView(true, $"暂无数据!"));
  362. }
  363. var hwIds = _sqlSugar.Queryable<Sys_SetData>()
  364. .Where(x => x.IsDel == 0 && x.STid == 10 && x.Name.Contains("会务活动"))
  365. .Select(x => x.Id)
  366. .ToList();
  367. var object_hwIds = hwIds.ConvertAll<object>(x => x);
  368. var searchRequest = new DynamicSearchRequest
  369. {
  370. Keyword = keyword,
  371. RequireAllSingleChars = true,
  372. PageIndex = 1,
  373. PageSize = 999999,
  374. FieldWeights = new Dictionary<string, int>
  375. {
  376. { "TeamName", 10 }
  377. },
  378. Filters = new List<SearchFilter>()
  379. {
  380. new(){Field = "IsDel",Operator="eq",Value="0" },
  381. new(){Field = "TeamDid",Operator="in",Values=object_hwIds }
  382. },
  383. OrderBy = "VisitDate",
  384. ReturnFields = new List<string>() { "TeamName" }
  385. };
  386. // 验证字段配置
  387. var validation = _groupSearchService.ValidateFieldConfig(
  388. searchRequest.FieldWeights,
  389. searchRequest.ReturnFields);
  390. if (!validation.IsValid)
  391. {
  392. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  393. }
  394. var result = await _groupSearchService.SearchAsync(searchRequest);
  395. if (result.Success)
  396. {
  397. var data = new List<dynamic>();
  398. foreach (var item in result.Items)
  399. {
  400. data.Add(new
  401. {
  402. item.Data.Id,
  403. item.Data.TeamName,
  404. });
  405. }
  406. return Ok(JsonView(true, result.Message, data, data.Count));
  407. }
  408. return Ok(JsonView(true, result.Message));
  409. }
  410. catch (Exception ex)
  411. {
  412. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  413. }
  414. }
  415. #region 团组费用-机票费用
  416. /// <summary>
  417. /// 团组机票费用基本信息
  418. /// </summary>
  419. /// <param name="groupIds"></param>
  420. /// <returns></returns>
  421. private async Task<List<AirInfo>> GetAirInfos(List<int> groupIds)
  422. {
  423. // 1. 查询
  424. var groupInfos = await _sqlSugar.Queryable<Grp_DelegationInfo>()
  425. .Where(x => x.IsDel == 0 && groupIds.Contains(x.Id))
  426. .Select(x => new { x.Id, x.TeamName })
  427. .ToListAsync();
  428. var airInfos = await _sqlSugar.Queryable<Grp_AirTicketReservations>()
  429. .Where(x => x.IsDel == 0 && groupIds.Contains(x.DIId) && !string.IsNullOrEmpty(x.TicketCode))
  430. .Select(x => new { x.DIId, x.TicketCode, x.ClientName })
  431. .ToListAsync();
  432. // 2. 解析客户名称ID
  433. var clientIds = new HashSet<int>();
  434. foreach (var item in airInfos)
  435. {
  436. if (string.IsNullOrWhiteSpace(item.ClientName))
  437. continue;
  438. var ids = item.ClientName.Split(',')
  439. .Select(s => s.Trim())
  440. .Where(s => int.TryParse(s, out _))
  441. .Select(int.Parse);
  442. foreach (var id in ids)
  443. {
  444. clientIds.Add(id);
  445. }
  446. }
  447. // 3. 批量查询客户名称
  448. var clientNames = new Dictionary<int, string>();
  449. if (clientIds.Any())
  450. {
  451. var nameInfos = await _sqlSugar.Queryable<Crm_DeleClient>()
  452. .Where(x => x.IsDel == 0 && clientIds.Contains(x.Id) &&
  453. !string.IsNullOrEmpty(x.FirstName) && !string.IsNullOrEmpty(x.LastName))
  454. .Select(x => new { x.Id, x.FirstName, x.LastName })
  455. .ToListAsync();
  456. clientNames = nameInfos.ToDictionary(
  457. x => x.Id,
  458. x => AesEncryptionHelper.Decrypt(x.LastName) + AesEncryptionHelper.Decrypt(x.FirstName)
  459. );
  460. }
  461. // 4. 构建 ClientName 到客户名称列表的映射(预解析)
  462. var clientNameCache = new Dictionary<string, List<string>>();
  463. foreach (var item in airInfos)
  464. {
  465. if (string.IsNullOrWhiteSpace(item.ClientName))
  466. continue;
  467. if (!clientNameCache.ContainsKey(item.ClientName))
  468. {
  469. var ids = item.ClientName.Split(',')
  470. .Select(s => s.Trim())
  471. .Where(s => int.TryParse(s, out _))
  472. .Select(int.Parse)
  473. .ToList();
  474. var names = ids.SelectMany(id =>
  475. clientNames.ContainsKey(id)
  476. ? new[] { clientNames[id] }
  477. : Array.Empty<string>()
  478. ).ToList();
  479. clientNameCache[item.ClientName] = names;
  480. }
  481. }
  482. // 5. 构建分组数据
  483. var airLookup = airInfos
  484. .GroupBy(x => x.DIId)
  485. .ToDictionary(g => g.Key, g => g.Select(x => x.TicketCode).Distinct().ToList());
  486. var clientLookup = airInfos
  487. .Where(x => !string.IsNullOrEmpty(x.ClientName) && clientNameCache.ContainsKey(x.ClientName))
  488. .GroupBy(x => x.DIId)
  489. .ToDictionary(g => g.Key, g => g.SelectMany(x => clientNameCache[x.ClientName]).Distinct().ToList());
  490. // 6. 组装结果
  491. var result = groupInfos.Select(group => new AirInfo
  492. {
  493. Id = group.Id,
  494. TeamName = group.TeamName,
  495. TicketCodes = airLookup.GetValueOrDefault(group.Id, new List<string>()),
  496. Clients = clientLookup.GetValueOrDefault(group.Id, new List<string>())
  497. }).ToList();
  498. return result;
  499. }
  500. public class AirInfo
  501. {
  502. public int Id { get; set; }
  503. public string TeamName { get; set; }
  504. public string TicketCodesLabel => string.Join("、", TicketCodes);
  505. public string ClientsLabel => string.Join("、", Clients);
  506. public List<string> TicketCodes { get; set; }
  507. public List<string> Clients { get; set; }
  508. }
  509. #endregion
  510. /// <summary>
  511. /// 团组各项费用录入 关键字输入提示(智能版)
  512. /// 76 酒店预订
  513. /// 79 车/导游地接
  514. /// 80 签证
  515. /// 81 邀请/公务活动
  516. /// 82 团组客户保险
  517. /// 85 机票预订
  518. /// 98 其他款项
  519. /// 285 收款退还
  520. /// 1015 超支费用
  521. /// 1081 文档下载
  522. /// 1466 会务相关
  523. /// </summary>
  524. /// <param name="userId">用户Id</param>
  525. /// <param name="feeType">费用类型</param>
  526. /// <param name="keyword">关键字</param>
  527. /// <returns></returns>
  528. [HttpGet("GroupFeeKeywordSearch/{userId}/{feeType}/{keyword}")]
  529. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  530. public async Task<IActionResult> GroupFeeKeywordSearch(int userId, int feeType, string keyword)
  531. {
  532. var stopwatch = Stopwatch.StartNew();
  533. try
  534. {
  535. #region 参数验证
  536. // 基本参数验证
  537. if (userId <= 0)
  538. return Ok(JsonView(false, "用户ID必须大于0!"));
  539. if (feeType <= 0)
  540. return Ok(JsonView(false, "费用类型必须大于0!"));
  541. // 验证用户是否存在
  542. var userExists = await _sqlSugar.Queryable<Sys_Users>()
  543. .Where(x => x.IsDel == 0 && x.Id == userId)
  544. .AnyAsync();
  545. if (!userExists) return Ok(JsonView(false, "用户不存在或已被删除"));
  546. // 验证费用类型是否有效
  547. var feeTypeExists = await _sqlSugar.Queryable<Sys_SetData>()
  548. .Where(x => x.IsDel == 0 && x.STid == 16 && x.Id == feeType)
  549. .AnyAsync();
  550. if (!feeTypeExists) return Ok(JsonView(false, "无效的费用类型"));
  551. // 验证关键字
  552. if (string.IsNullOrWhiteSpace(keyword)) return Ok(JsonView(false, "请输入搜索关键字"));
  553. #endregion
  554. #region 获取用户有权限的团组ID
  555. // 获取用户有权限访问的团组ID列表
  556. var authorizedGroupIds = await _sqlSugar.Queryable<Grp_GroupsTaskAssignment>()
  557. .Where(x => x.IsDel == 0 && x.UId == userId && x.CTId == feeType)
  558. .Select(x => x.DIId)
  559. .Distinct()
  560. .ToListAsync();
  561. if (!authorizedGroupIds.Any()) return Ok(JsonView(true, "暂无数据", new List<object>(), 0));
  562. #endregion
  563. // 类型等于 85 时单独处理
  564. if (feeType == 85)
  565. {
  566. var airInfos = await GetAirInfos(authorizedGroupIds);
  567. if (airInfos == null) return Ok(JsonView(false, "获取机票费用信息失败"));
  568. var airSearchReq = new DynamicSearchRequest()
  569. {
  570. Keyword = keyword.Trim(),
  571. RequireAllSingleChars = true,
  572. PageIndex = 1,
  573. PageSize = 20, // 限制返回数量,提高性能
  574. FieldWeights = new Dictionary<string, int>
  575. {
  576. { "TeamName", 10 },
  577. { "TicketCodesLabel", 10 },
  578. { "ClientsLabel", 10 }
  579. }
  580. };
  581. var airResult = _airSearchService.SearchDataSource(airSearchReq, airInfos);
  582. if (!airResult.Success)
  583. {
  584. return Ok(JsonView(false, airResult.Message ?? "搜索失败"));
  585. }
  586. if (airResult.Items == null || !airResult.Items.Any())
  587. {
  588. return Ok(JsonView(true, "未找到匹配的团组", new List<object>(), 0));
  589. }
  590. var airRespData = airResult.Items
  591. .Where(item => item.Data != null)
  592. .Select(item => new
  593. {
  594. item.Data.Id,
  595. item.Data.TeamName,
  596. item.Data.TicketCodes,
  597. item.Data.Clients,
  598. })
  599. .Where(x => !string.IsNullOrWhiteSpace(x.TeamName)) // 过滤空名称
  600. .Distinct() // 去重
  601. .ToList();
  602. stopwatch.Stop();
  603. return Ok(JsonView(true, $"搜索成功,耗时:{stopwatch.ElapsedMilliseconds}ms", airRespData, airRespData.Count));
  604. }
  605. #region 构建搜索请求
  606. var searchRequest = new DynamicSearchRequest
  607. {
  608. Keyword = keyword.Trim(),
  609. RequireAllSingleChars = true,
  610. PageIndex = 1,
  611. PageSize = 20, // 限制返回数量,提高性能
  612. FieldWeights = new Dictionary<string, int>
  613. {
  614. { "TeamName", 10 }
  615. },
  616. Filters = new List<SearchFilter>
  617. {
  618. new SearchFilter { Field = "IsDel", Operator = "eq", Value = "0" },
  619. new SearchFilter { Field = "Id", Operator = "in", Values = authorizedGroupIds.ConvertAll<object>(x => x) }
  620. },
  621. OrderBy = "VisitDate", // 添加排序方向
  622. ReturnFields = new List<string> { "Id", "TeamName" } // 添加ID字段
  623. };
  624. #endregion
  625. #region 字段配置验证
  626. var validation = _groupSearchService.ValidateFieldConfig(
  627. searchRequest.FieldWeights,
  628. searchRequest.ReturnFields);
  629. if (!validation.IsValid)
  630. {
  631. return Ok(JsonView(false, $"字段配置错误: {validation.Message}"));
  632. }
  633. #endregion
  634. #region 执行搜索
  635. var result = await _groupSearchService.SearchAsync(searchRequest);
  636. if (!result.Success)
  637. {
  638. return Ok(JsonView(false, result.Message ?? "搜索失败"));
  639. }
  640. if (result.Items == null || !result.Items.Any())
  641. {
  642. return Ok(JsonView(true, "未找到匹配的团组", new List<object>(), 0));
  643. }
  644. #endregion
  645. #region 构建返回数据
  646. var responseData = result.Items
  647. .Where(item => item.Data != null)
  648. .Select(item => new
  649. {
  650. Id = item.Data.Id,
  651. TeamName = item.Data.TeamName
  652. })
  653. .Where(x => !string.IsNullOrWhiteSpace(x.TeamName)) // 过滤空名称
  654. .Distinct() // 去重
  655. .ToList();
  656. #endregion
  657. stopwatch.Stop();
  658. return Ok(JsonView(true, $"搜索成功,耗时:{stopwatch.ElapsedMilliseconds} ms", responseData, responseData.Count));
  659. }
  660. catch (Exception ex)
  661. {
  662. // 记录日志(实际项目中应该使用日志框架)
  663. // _logger.LogError(ex, "团组费用关键字搜索失败,用户ID: {UserId}, 费用类型: {FeeType}", userId, feeType);
  664. // 生产环境中不要返回详细的错误信息
  665. return Ok(JsonView(false, "搜索服务暂时不可用,请稍后重试"));
  666. }
  667. }
  668. /// <summary>
  669. /// 三公-接团信息 关键字输入提示(单字段)
  670. /// </summary>
  671. /// <param name="currUserId">用户名称ID</param>
  672. /// <param name="keyword">关键字</param>
  673. /// <returns></returns>
  674. [HttpGet("GroupEnterExitCostKeywordSearch/{currUserId}/{keyword}")]
  675. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  676. public async Task<IActionResult> GroupEnterExitCostKeywordSearch(int currUserId, string keyword)
  677. {
  678. try
  679. {
  680. // 验证请求参数
  681. if (currUserId < 1)
  682. {
  683. return Ok(JsonView(true, $"请传入有效的用户ID!"));
  684. }
  685. if (string.IsNullOrEmpty(keyword))
  686. {
  687. return Ok(JsonView(true, $"暂无数据!"));
  688. }
  689. var searchRequest = new DynamicSearchRequest
  690. {
  691. Keyword = keyword,
  692. RequireAllSingleChars = true,
  693. PageIndex = 1,
  694. PageSize = 30,
  695. FieldWeights = new Dictionary<string, int>
  696. {
  697. { "TeamName", 10 },
  698. //{ "ClientUnit", 8 },
  699. //{ "ClientName", 6 }
  700. },
  701. Filters = new List<SearchFilter>()
  702. {
  703. new(){Field = "IsDel",Operator="eq",Value="0" }
  704. },
  705. OrderBy = "TeamName",
  706. ReturnFields = new List<string>() { "TeamName" }
  707. };
  708. // 验证字段配置
  709. var validation = _groupSearchService.ValidateFieldConfig(
  710. searchRequest.FieldWeights,
  711. searchRequest.ReturnFields);
  712. if (!validation.IsValid)
  713. {
  714. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  715. }
  716. var result = await _groupSearchService.SearchAsync(searchRequest);
  717. if (result.Success)
  718. {
  719. var groupIds = result.Items.Select(x => x.Data.Id).ToList();
  720. var isNullDatas = _sqlSugar.Queryable<Grp_EnterExitCost>()
  721. .Where(x => x.IsDel == 0 && groupIds.Contains(x.DiId))
  722. .Select(x => x.DiId)
  723. .Distinct()
  724. .ToList();
  725. var isViewDatas = _sqlSugar.Queryable<Grp_EnterExitCostPermission>()
  726. .Where(x1 => x1.IsDel == 0 && x1.UserId == currUserId && groupIds.Contains(x1.GroupId))
  727. .Select(x1 => x1.GroupId)
  728. .Distinct()
  729. .ToList();
  730. var provCityDatas = await _groupRep.ProvinceCityBasicSource();
  731. var data = result.Items.Select(x =>
  732. {
  733. int groupId = x.Data.Id;
  734. int cityId = x.Data.CityId;
  735. bool isNull = !isNullDatas.Where(x => x == groupId).Any();
  736. bool isView = isViewDatas.Where(x => x == groupId).Any();
  737. var provinceId = 122; //默认四川
  738. if (provinceId > 0)
  739. {
  740. var parentId = _groupRep.FindParentIdByChildId(provCityDatas, cityId);
  741. if (parentId != null)
  742. {
  743. provinceId = (int)parentId;
  744. }
  745. }
  746. return new
  747. {
  748. x.Data.Id,
  749. groupName = x.Data.TeamName,
  750. provinceId,
  751. isNull,
  752. isView,
  753. };
  754. }).ToList();
  755. return Ok(JsonView(true, result.Message, data, data.Count));
  756. }
  757. return Ok(JsonView(true, result.Message));
  758. }
  759. catch (Exception ex)
  760. {
  761. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  762. }
  763. }
  764. /// <summary>
  765. /// 商邀AI invName关键字输入提示(单字段)
  766. /// </summary>
  767. /// <param name="keyword">关键字</param>
  768. /// <returns></returns>
  769. [HttpGet("InvAIKeywordSearch/{keyword}")]
  770. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  771. public async Task<IActionResult> InvAIKeywordSearch(string keyword)
  772. {
  773. try
  774. {
  775. // 验证请求参数 _invAISearchService
  776. if (string.IsNullOrEmpty(keyword))
  777. {
  778. return Ok(JsonView(true, $"暂无数据!"));
  779. }
  780. var invNames = await GeneralMethod.InvitationAIInvName();
  781. var searchRequest = new DynamicSearchRequest
  782. {
  783. Keyword = keyword,
  784. RequireAllSingleChars = true,
  785. PageIndex = 1,
  786. PageSize = 999999,
  787. FieldWeights = new Dictionary<string, int>
  788. {
  789. { "Name", 10 },
  790. },
  791. OrderBy = "SortTime"
  792. };
  793. // 验证字段配置
  794. var validation = _invAISearchService.ValidateFieldConfig(
  795. searchRequest.FieldWeights,
  796. searchRequest.ReturnFields);
  797. if (!validation.IsValid)
  798. {
  799. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  800. }
  801. var result = _invAISearchService.SearchDataSource(searchRequest, invNames);
  802. if (result.Success)
  803. {
  804. var view = result.Items.Select(x => x.Data).ToList();
  805. return Ok(JsonView(true, result.Message, view, view.Count));
  806. }
  807. return Ok(JsonView(true, "暂无数据"));
  808. }
  809. catch (Exception ex)
  810. {
  811. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  812. }
  813. }
  814. /// <summary>
  815. /// 商邀AI ClientName 关键字输入提示(单字段)
  816. /// </summary>
  817. /// <param name="keyword">关键字</param>
  818. /// <returns></returns>
  819. [HttpGet("InvAIClientKeywordSearch/{keyword}")]
  820. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  821. public async Task<IActionResult> InvAIClientKeywordSearch(string keyword)
  822. {
  823. try
  824. {
  825. // 验证请求参数 _invAISearchService
  826. if (string.IsNullOrEmpty(keyword))
  827. {
  828. return Ok(JsonView(true, $"暂无数据!"));
  829. }
  830. var invNames = await GeneralMethod.InvitationAIClientName();
  831. var data = invNames.Select(x =>
  832. {
  833. return new InvitationAIInvNameView
  834. {
  835. Name = x,
  836. SortTime = DateTime.Now
  837. };
  838. }).ToList();
  839. var searchRequest = new DynamicSearchRequest
  840. {
  841. Keyword = keyword,
  842. RequireAllSingleChars = true,
  843. PageIndex = 1,
  844. PageSize = 999999,
  845. FieldWeights = new Dictionary<string, int>
  846. {
  847. { "Name", 10 },
  848. },
  849. OrderBy = "SortTime"
  850. };
  851. // 验证字段配置
  852. var validation = _invAISearchService.ValidateFieldConfig(
  853. searchRequest.FieldWeights,
  854. searchRequest.ReturnFields);
  855. if (!validation.IsValid)
  856. {
  857. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  858. }
  859. var result = _invAISearchService.SearchDataSource(searchRequest, data);
  860. if (result.Success)
  861. {
  862. var view = result.Items.Select(x => x.Data.Name).ToList();
  863. if (view.Count == 0)
  864. {
  865. return Ok(JsonView(true, "暂无数据"));
  866. }
  867. return Ok(JsonView(true, result.Message, view, view.Count));
  868. }
  869. return Ok(JsonView(true, "暂无数据"));
  870. }
  871. catch (Exception ex)
  872. {
  873. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  874. }
  875. }
  876. /// <summary>
  877. /// 商邀AI CountryName 关键字输入提示(单字段)
  878. /// </summary>
  879. /// <param name="keyword">关键字</param>
  880. /// <returns></returns>
  881. [HttpGet("InvAICountryKeywordSearch/{keyword}")]
  882. [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
  883. public async Task<IActionResult> InvAICountryKeywordSearch(string keyword)
  884. {
  885. try
  886. {
  887. // 验证请求参数 _invAISearchService
  888. if (string.IsNullOrEmpty(keyword))
  889. {
  890. return Ok(JsonView(true, $"暂无数据!"));
  891. }
  892. var countryNames = await GeneralMethod.InvitationAICountryName();
  893. var data = countryNames.Select(x =>
  894. {
  895. return new InvitationAIInvNameView
  896. {
  897. Name = x,
  898. SortTime = DateTime.Now
  899. };
  900. }).ToList();
  901. var searchRequest = new DynamicSearchRequest
  902. {
  903. Keyword = keyword,
  904. RequireAllSingleChars = true,
  905. PageIndex = 1,
  906. PageSize = 999999,
  907. FieldWeights = new Dictionary<string, int>
  908. {
  909. { "Name", 10 },
  910. },
  911. OrderBy = "SortTime"
  912. };
  913. // 验证字段配置
  914. var validation = _invAISearchService.ValidateFieldConfig(
  915. searchRequest.FieldWeights,
  916. searchRequest.ReturnFields);
  917. if (!validation.IsValid)
  918. {
  919. return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
  920. }
  921. var result = _invAISearchService.SearchDataSource(searchRequest, data);
  922. if (result.Success)
  923. {
  924. var view = result.Items.Select(x => x.Data.Name).ToList();
  925. if (view.Count == 0)
  926. {
  927. return Ok(JsonView(true, "暂无数据"));
  928. }
  929. return Ok(JsonView(true, result.Message, view, view.Count));
  930. }
  931. return Ok(JsonView(true, "暂无数据"));
  932. }
  933. catch (Exception ex)
  934. {
  935. return Ok(JsonView(true, $"搜索服务暂时不可用!"));
  936. }
  937. }
  938. }
  939. }