diff --git a/ModeusSchedule.Abstractions/DTO/ModeusScheduleRequestDTO.cs b/ModeusSchedule.Abstractions/DTO/Requests/ModeusScheduleRequest.cs
similarity index 98%
rename from ModeusSchedule.Abstractions/DTO/ModeusScheduleRequestDTO.cs
rename to ModeusSchedule.Abstractions/DTO/Requests/ModeusScheduleRequest.cs
index 7fb1fd1..251cff9 100644
--- a/ModeusSchedule.Abstractions/DTO/ModeusScheduleRequestDTO.cs
+++ b/ModeusSchedule.Abstractions/DTO/Requests/ModeusScheduleRequest.cs
@@ -1,6 +1,6 @@
using System.ComponentModel;
-namespace ModeusSchedule.Abstractions.DTO;
+namespace ModeusSchedule.Abstractions.DTO.Requests;
///
/// DTO для запроса расписания в Modeus.
diff --git a/ModeusSchedule.Abstractions/DTO/Requests/ModeusSearchPersonRequest.cs b/ModeusSchedule.Abstractions/DTO/Requests/ModeusSearchPersonRequest.cs
new file mode 100644
index 0000000..ec62b40
--- /dev/null
+++ b/ModeusSchedule.Abstractions/DTO/Requests/ModeusSearchPersonRequest.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+
+namespace SfeduSchedule.DTO.Requests;
+
+public record ModeusSearchPersonRequest(
+ [property: JsonPropertyName("fullName")]
+ string FullName = "",
+
+ [property: JsonPropertyName("sort")]
+ string Sort = "+fullName",
+
+ [property: JsonPropertyName("size")]
+ int Size = 10,
+
+ [property: JsonPropertyName("page")]
+ int Page = 0
+);
\ No newline at end of file
diff --git a/ModeusSchedule.Abstractions/DTO/Responses/ModeusSearchPersonResponse.cs b/ModeusSchedule.Abstractions/DTO/Responses/ModeusSearchPersonResponse.cs
new file mode 100644
index 0000000..b04e79e
--- /dev/null
+++ b/ModeusSchedule.Abstractions/DTO/Responses/ModeusSearchPersonResponse.cs
@@ -0,0 +1,84 @@
+// Auto-generated by https://json2csharp.com/
+
+using System.Text.Json.Serialization;
+
+namespace SfeduSchedule.DTO.Responses;
+
+// SearchEmployeesResponse myDeserializedClass = JsonSerializer.Deserialize(myJsonResponse);
+
+public record Embedded(
+ [property: JsonPropertyName("persons")]
+ IReadOnlyList Persons,
+ [property: JsonPropertyName("employees")]
+ IReadOnlyList Employees,
+ [property: JsonPropertyName("students")]
+ IReadOnlyList Students
+);
+
+public record Employee(
+ [property: JsonPropertyName("id")] string Id,
+ [property: JsonPropertyName("personId")]
+ string PersonId,
+ [property: JsonPropertyName("groupId")]
+ string GroupId,
+ [property: JsonPropertyName("groupName")]
+ string GroupName,
+ [property: JsonPropertyName("dateIn")] string DateIn,
+ [property: JsonPropertyName("dateOut")]
+ string DateOut
+);
+
+public record Links(
+ [property: JsonPropertyName("self")] Self Self
+);
+
+public record Page(
+ [property: JsonPropertyName("size")] int? Size,
+ [property: JsonPropertyName("totalElements")]
+ int? TotalElements,
+ [property: JsonPropertyName("totalPages")]
+ int? TotalPages,
+ [property: JsonPropertyName("number")] int? Number
+);
+
+public record Person(
+ [property: JsonPropertyName("lastName")]
+ string LastName,
+ [property: JsonPropertyName("firstName")]
+ string FirstName,
+ [property: JsonPropertyName("middleName")]
+ string MiddleName,
+ [property: JsonPropertyName("fullName")]
+ string FullName,
+ [property: JsonPropertyName("_links")] Links Links,
+ [property: JsonPropertyName("id")] string Id
+);
+
+public record ModeusSearchPersonResponse(
+ [property: JsonPropertyName("_embedded")]
+ Embedded Embedded,
+ [property: JsonPropertyName("page")] Page Page
+);
+
+public record Self(
+ [property: JsonPropertyName("href")] string Href
+);
+
+public record Student(
+ [property: JsonPropertyName("id")] string Id,
+ [property: JsonPropertyName("personId")]
+ string PersonId,
+ [property: JsonPropertyName("flowId")] string FlowId,
+ [property: JsonPropertyName("flowCode")]
+ string FlowCode,
+ [property: JsonPropertyName("specialtyCode")]
+ string SpecialtyCode,
+ [property: JsonPropertyName("specialtyName")]
+ string SpecialtyName,
+ [property: JsonPropertyName("specialtyProfile")]
+ string SpecialtyProfile,
+ [property: JsonPropertyName("learningStartDate")]
+ DateTime? LearningStartDate,
+ [property: JsonPropertyName("learningEndDate")]
+ DateTime? LearningEndDate
+);
\ No newline at end of file
diff --git a/ModeusSchedule.Abstractions/DTO/Responses/SearchPersonResponse.cs b/ModeusSchedule.Abstractions/DTO/Responses/SearchPersonResponse.cs
new file mode 100644
index 0000000..b3e9643
--- /dev/null
+++ b/ModeusSchedule.Abstractions/DTO/Responses/SearchPersonResponse.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+
+namespace SfeduSchedule.DTO.Responses;
+
+public record SearchPersonResponse(
+ IReadOnlyList Persons
+);
+
+public record SearchPerson(
+ [property: JsonPropertyName("name")]
+ string Name,
+ [property: JsonPropertyName("person_id")]
+ string PersonId
+);
\ No newline at end of file
diff --git a/SfeduSchedule/Controllers/ProxyController.cs b/SfeduSchedule/Controllers/ProxyController.cs
index c4f4b75..d22670e 100644
--- a/SfeduSchedule/Controllers/ProxyController.cs
+++ b/SfeduSchedule/Controllers/ProxyController.cs
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using ModeusSchedule.Abstractions;
using ModeusSchedule.Abstractions.DTO;
+using ModeusSchedule.Abstractions.DTO.Requests;
using SfeduSchedule.Services;
namespace SfeduSchedule.Controllers;
diff --git a/SfeduSchedule/Controllers/ScheduleController.cs b/SfeduSchedule/Controllers/ScheduleController.cs
index 25a5141..cf5ed31 100644
--- a/SfeduSchedule/Controllers/ScheduleController.cs
+++ b/SfeduSchedule/Controllers/ScheduleController.cs
@@ -1,8 +1,11 @@
+using System.ComponentModel.DataAnnotations;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using ModeusSchedule.Abstractions.DTO;
+using ModeusSchedule.Abstractions.DTO.Requests;
+using SfeduSchedule.DTO.Responses;
using SfeduSchedule.Services;
namespace SfeduSchedule.Controllers;
@@ -10,13 +13,13 @@ namespace SfeduSchedule.Controllers;
[ApiController]
[Route("api/schedule")]
[EnableRateLimiting("throttle")]
-public class ScheduleController(ModeusService modeusService, ILogger logger) : ControllerBase
+public class ScheduleController(ModeusService modeusService, ModeusEmployeeService modeusEmployeeService, ILogger logger) : ControllerBase
{
///
- /// Получить GUID пользователя по полному имени. (требуется авторизация)
+ /// Получить GUID пользователя по полному имени. (включая студентов, требуется авторизация)
///
/// Полное имя пользователя.
- /// GUID пользователя.
+ /// GUID пользователя
/// Возвращает GUID пользователя
/// Пользователь не найден
/// Неавторизованный
@@ -32,6 +35,29 @@ public class ScheduleController(ModeusService modeusService, ILogger
+ /// Поиск сотрудников по имени. (преподавателей)
+ ///
+ /// ФИО Сотрудника
+ /// Список сотрудников (до 10 записей)
+ /// Возвращает список сотрудников с их GUID
+ /// Сотрудник не найден
+ /// Неавторизованный
+ [HttpGet]
+ [Route("searchemployee")]
+ public async Task SearchEmployees([Required][MinLength(1)] string fullname)
+ {
+ var employees = await modeusEmployeeService.GetEmployees(fullname, 10);
+ if (employees.Count == 0)
+ return NotFound();
+
+ var persons = new List(employees.Count);
+ foreach (var employee in employees)
+ persons.Add(new SearchPerson(Name: employee.Key, PersonId: employee.Value.Item1));
+
+ return Ok(new SearchPersonResponse(Persons: persons).Persons);
+ }
///
/// Получить расписание в формате ICS по пользовательскому запросу.
diff --git a/SfeduSchedule/Program.cs b/SfeduSchedule/Program.cs
index 2047936..d029298 100644
--- a/SfeduSchedule/Program.cs
+++ b/SfeduSchedule/Program.cs
@@ -92,6 +92,7 @@ builder.Services.AddHttpClient("modeus", client =>
client.BaseAddress = new Uri(configuration["MODEUS_URL"]!);
});
builder.Services.AddSingleton();
+builder.Services.AddHostedService();
builder.Services.AddSingleton();
builder.Services.AddHttpClient("authClient");
diff --git a/SfeduSchedule/Services/ModeusEmployeeService.cs b/SfeduSchedule/Services/ModeusEmployeeService.cs
new file mode 100644
index 0000000..0505847
--- /dev/null
+++ b/SfeduSchedule/Services/ModeusEmployeeService.cs
@@ -0,0 +1,75 @@
+using SfeduSchedule.Logging;
+
+namespace SfeduSchedule.Services;
+
+public class ModeusEmployeeService(ILogger logger, ModeusService modeusService)
+ : IHostedService
+{
+ private Dictionary)> _employees = [];
+ private Task? _backgroundTask;
+ private CancellationTokenSource? _cts;
+
+ public async Task)>> GetEmployees(string fullname, int size = 10)
+ {
+ return _employees
+ .Where(e => e.Key.Contains(fullname, StringComparison.OrdinalIgnoreCase))
+ .Take(size)
+ .ToDictionary(e => e.Key, e => e.Value);
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ _backgroundTask = Task.Run(async () =>
+ {
+ try
+ {
+ await Task.Delay(TimeSpan.FromSeconds(15), _cts.Token);
+
+ var employees = await modeusService.GetEmployeesAsync();
+ if (employees.Count == 0)
+ {
+ logger.LogWarningHere("Не удалось получить список сотрудников из Modeus.");
+ }
+ else
+ {
+ _employees = employees;
+ logger.LogInformationHere($"Получено {employees.Count} сотрудников из Modeus.");
+ }
+
+ await Task.Delay(TimeSpan.FromSeconds(5), _cts.Token);
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+ catch (OperationCanceledException)
+ {
+ // ignore
+ }
+ catch (Exception ex)
+ {
+ logger.LogErrorHere(ex, "Ошибка при загрузке сотрудников из Modeus.");
+ }
+ }, _cts.Token);
+
+ return Task.CompletedTask;
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken)
+ {
+ if (_cts is null || _backgroundTask is null)
+ {
+ return;
+ }
+
+ _cts.Cancel();
+
+ try
+ {
+ await Task.WhenAny(_backgroundTask, Task.Delay(TimeSpan.FromSeconds(5), cancellationToken));
+ }
+ catch (OperationCanceledException)
+ {
+ // ignore
+ }
+ }
+}
\ No newline at end of file
diff --git a/SfeduSchedule/Services/ModeusHttpClient.cs b/SfeduSchedule/Services/ModeusHttpClient.cs
index 73dc8cb..015cb9a 100644
--- a/SfeduSchedule/Services/ModeusHttpClient.cs
+++ b/SfeduSchedule/Services/ModeusHttpClient.cs
@@ -1,8 +1,12 @@
+using System.Diagnostics;
using System.Text;
using System.Text.Json;
using Microsoft.Net.Http.Headers;
using ModeusSchedule.Abstractions;
using ModeusSchedule.Abstractions.DTO;
+using ModeusSchedule.Abstractions.DTO.Requests;
+using SfeduSchedule.DTO.Requests;
+using SfeduSchedule.DTO.Responses;
using SfeduSchedule.Logging;
namespace SfeduSchedule.Services;
@@ -67,7 +71,44 @@ public class ModeusHttpClient
_logger.LogErrorHere(ex, "Deserialization failed.");
}
- return new List();
+ return [];
+ }
+
+ public async Task SearchPersonAsync(ModeusSearchPersonRequest modeusSearchPersonRequest)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Post,
+ $"schedule-calendar-v2/api/people/persons/search");
+ request.Content = new StringContent(
+ JsonSerializer.Serialize(modeusSearchPersonRequest, GlobalConsts.JsonSerializerOptions),
+ Encoding.UTF8, "application/json");
+ var stopwatch = Stopwatch.StartNew();
+ using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
+ var requestMs = stopwatch.ElapsedMilliseconds;
+ if (response.StatusCode != System.Net.HttpStatusCode.OK)
+ {
+ _logger.LogErrorHere($"Неуспешный статус при получении расписания: {response.StatusCode}, Поле ФИО: \"{modeusSearchPersonRequest.FullName}\"");
+ return null;
+ }
+
+ try
+ {
+ var deserializeStartMs = stopwatch.ElapsedMilliseconds;
+ await using var contentStream = await response.Content.ReadAsStreamAsync();
+ var content = await JsonSerializer.DeserializeAsync(
+ contentStream,
+ GlobalConsts.JsonSerializerOptions);
+ var groupMs = stopwatch.ElapsedMilliseconds - deserializeStartMs;
+
+ _logger.LogInformationHere($"SearchPersonAsync: Request time: {requestMs} ms, Deserialization time: {groupMs} ms, Total time: {stopwatch.ElapsedMilliseconds} ms.");
+
+ return content;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogErrorHere(ex, "SearchPersonAsync: Deserialization failed.");
+ }
+
+ return null;
}
public async Task SearchRoomsAsync(RoomSearchRequest requestDto)
@@ -85,43 +126,4 @@ public class ModeusHttpClient
return await response.Content.ReadAsStringAsync();
}
- public async Task GetGuidAsync(string fullName)
- {
- var request = new HttpRequestMessage(HttpMethod.Post, "schedule-calendar-v2/api/people/persons/search");
- request.Content = new StringContent(JsonSerializer.Serialize(new
- {
- fullName,
- sort = "+fullName",
- size = 10,
- page = 0
- }), Encoding.UTF8, "application/json");
- var response = await _httpClient.SendAsync(request);
-
- _logger.LogInformationHere($"Ответ получен: {response.StatusCode}");
- if (response.StatusCode != System.Net.HttpStatusCode.OK)
- {
- _logger.LogErrorHere($"Неуспешный статус при получении расписания: {response.StatusCode}, Name: {fullName}");
- return null;
- }
-
- var json = await response.Content.ReadAsStringAsync();
-
- string? personId;
- try
- {
- personId = JsonDocument.Parse(json).RootElement
- .GetProperty("_embedded")
- .GetProperty("persons")[0]
- .GetProperty("id")
- .GetString();
- }
- catch
- {
- _logger.LogWarningHere($"Не удалось получить идентификатор пользователя. FullName={fullName}");
- return null;
- }
-
- return personId;
- }
-
}
\ No newline at end of file
diff --git a/SfeduSchedule/Services/ModeusService.cs b/SfeduSchedule/Services/ModeusService.cs
index dc330db..8031f52 100644
--- a/SfeduSchedule/Services/ModeusService.cs
+++ b/SfeduSchedule/Services/ModeusService.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics;
using System.Text.Json;
using Ical.Net;
using Ical.Net.CalendarComponents;
@@ -5,33 +6,25 @@ using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using ModeusSchedule.Abstractions;
using ModeusSchedule.Abstractions.DTO;
+using ModeusSchedule.Abstractions.DTO.Requests;
+using SfeduSchedule.DTO.Requests;
+using SfeduSchedule.DTO.Responses;
using SfeduSchedule.Logging;
namespace SfeduSchedule.Services;
-public class ModeusService
+public class ModeusService(
+ ILogger logger,
+ IConfiguration configuration,
+ ModeusHttpClient modeusHttpClient)
{
- private readonly IConfiguration _configuration;
-
- private readonly ILogger _logger;
- private readonly ModeusHttpClient _modeusHttpClient;
-
- public ModeusService(
- ILogger logger,
- IConfiguration configuration,
- ModeusHttpClient modeusHttpClient)
- {
- _modeusHttpClient = modeusHttpClient;
- _logger = logger;
- _configuration = configuration;
- }
-
public async Task GetScheduleJsonAsync(ModeusScheduleRequest msr)
{
var schedule = await GetScheduleAsync(msr);
if (schedule == null)
{
- _logger.LogErrorHere($"schedule is null. {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
+ logger.LogErrorHere(
+ $"schedule is null. {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
return null;
}
@@ -42,16 +35,20 @@ public class ModeusService
switch (scheduleJson)
{
case null:
- _logger.LogErrorHere($"scheduleJson is null. Schedule: {schedule}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
+ logger.LogErrorHere(
+ $"scheduleJson is null. Schedule: {schedule}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
case { Embedded: null }:
- _logger.LogErrorHere($"scheduleJson.Embedded is null. Response: {schedule}\nscheduleJson: {scheduleJson}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
+ logger.LogErrorHere(
+ $"scheduleJson.Embedded is null. Response: {schedule}\nscheduleJson: {scheduleJson}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
case { Embedded.Events: null }:
- _logger.LogErrorHere($"scheduleJson.Embedded.Events is null. Response: {schedule}\nscheduleJson: {scheduleJson}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
+ logger.LogErrorHere(
+ $"scheduleJson.Embedded.Events is null. Response: {schedule}\nscheduleJson: {scheduleJson}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
case { Embedded.Events.Length: 0 }:
- _logger.LogWarningHere($"scheduleJson.Embedded.Events is empty. Embedded: {scheduleJson.Embedded}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
+ logger.LogWarningHere(
+ $"scheduleJson.Embedded.Events is empty. Embedded: {scheduleJson.Embedded}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
default:
return scheduleJson;
@@ -59,7 +56,8 @@ public class ModeusService
}
catch (Exception ex)
{
- _logger.LogErrorHere($"Deserialization failed. Schedule: {schedule}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}\n Exception: {ex}");
+ logger.LogErrorHere(
+ $"Deserialization failed. Schedule: {schedule}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}\n Exception: {ex}");
}
return null;
@@ -70,12 +68,13 @@ public class ModeusService
var scheduleJson = await GetScheduleJsonAsync(msr);
if (scheduleJson == null)
{
- _logger.LogErrorHere($"scheduleJson is null after deserialization. Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
+ logger.LogErrorHere(
+ $"scheduleJson is null after deserialization. Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
return null;
}
var calendar = new Calendar();
- calendar.AddTimeZone(new VTimeZone(_configuration["TZ"]!));
+ calendar.AddTimeZone(new VTimeZone(configuration["TZ"]!));
foreach (var e in scheduleJson.Embedded.Events)
{
@@ -91,7 +90,7 @@ public class ModeusService
{
var eventRoomsLink = eventLocation.Links.EventRooms.Value;
var eventRoomsHref = eventRoomsLink.Self?.Href
- ?? eventRoomsLink.SelfArray?.FirstOrDefault()?.Href;
+ ?? eventRoomsLink.SelfArray?.FirstOrDefault()?.Href;
if (string.IsNullOrWhiteSpace(eventRoomsHref))
continue;
@@ -176,37 +175,150 @@ public class ModeusService
Summary = (string.IsNullOrEmpty(shortNameCourse) ? "" : shortNameCourse + " / ") + e.Name,
Description = e.NameShort + (string.IsNullOrEmpty(roomName) ? "" : $"\nАудитория: {roomName}") +
(string.IsNullOrEmpty(teachersNames) ? "" : $"\nПреподаватели: {teachersNames}"),
- Start = new CalDateTime(e.StartsAtLocal, _configuration["TZ"]!),
- End = new CalDateTime(e.EndsAtLocal, _configuration["TZ"]!)
+ Start = new CalDateTime(e.StartsAtLocal, configuration["TZ"]!),
+ End = new CalDateTime(e.EndsAtLocal, configuration["TZ"]!)
});
}
var serializer = new CalendarSerializer();
var serializedCalendar = serializer.SerializeToString(calendar);
- _logger.LogInformationHere($"serialized calendar created. Length: {serializedCalendar?.Length ?? 0}");
+ logger.LogInformationHere($"serialized calendar created. Length: {serializedCalendar?.Length ?? 0}");
return serializedCalendar;
}
+ public async Task)>> GetEmployeesAsync()
+ {
+ var searchPersonResponse =
+ await modeusHttpClient.SearchPersonAsync(new ModeusSearchPersonRequest() { Size = 38000 });
+ if (searchPersonResponse == null)
+ {
+ logger.LogErrorHere("persons is null");
+ return [];
+ }
+
+ var persons = searchPersonResponse.Embedded.Persons;
+ var employees = searchPersonResponse.Embedded.Employees;
+
+ var stopwatch = Stopwatch.StartNew();
+ var personsById = new Dictionary();
+ foreach (var p in persons)
+ {
+ var id = p?.Id;
+ if (!string.IsNullOrEmpty(id))
+ {
+ personsById[id] = p;
+ }
+ }
+
+ var mapMs = stopwatch.ElapsedMilliseconds;
+
+ var groupStartMs = stopwatch.ElapsedMilliseconds;
+ var grouped =
+ new Dictionary Positions)>(employees.Count,
+ StringComparer.Ordinal);
+
+
+ foreach (var e in employees)
+ {
+ if (e == null) continue;
+ var personId = e.PersonId ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(personId)) continue;
+
+ var fullName = personId;
+ if (personsById.TryGetValue(personId, out var person))
+ {
+ var name = (person.FullName ?? string.Empty).Trim();
+ if (!string.IsNullOrEmpty(name))
+ {
+ fullName = name;
+ }
+ }
+
+ var position = (e.GroupName ?? string.Empty).Trim();
+
+ static string FormatDateRange(string? dateIn, string? dateOut)
+ {
+ var start = (dateIn ?? string.Empty).Trim();
+ if (string.IsNullOrEmpty(start)) start = "?";
+
+ var end = (dateOut ?? string.Empty).Trim();
+ if (string.IsNullOrEmpty(end)) end = "по наст.вр.";
+
+ return $"{start}–{end}";
+ }
+
+ var dateRange = FormatDateRange(e.DateIn, e.DateOut);
+ var positionWithDates = string.IsNullOrEmpty(position) ? $"({dateRange})" : $"{position} ({dateRange})";
+
+ if (!grouped.TryGetValue(fullName, out var entry))
+ {
+ entry = (personId, new List());
+ }
+ else
+ {
+ if (string.IsNullOrEmpty(entry.PersonId) || entry.PersonId == personId)
+ {
+ entry.PersonId = personId;
+ }
+ }
+
+ if (!entry.Positions.Contains(positionWithDates))
+ {
+ entry.Positions.Add(positionWithDates);
+ }
+
+ grouped[fullName] = entry;
+ }
+
+ var groupMs = stopwatch.ElapsedMilliseconds - groupStartMs;
+ var totalMs = stopwatch.ElapsedMilliseconds;
+
+ logger.LogInformationHere(
+ $"GetEmployeesAsync timing: mapPersons={mapMs}ms, groupEmployees={groupMs}ms, total={totalMs}ms");
+
+ return grouped;
+ }
+
#region Проксирование методов из ModeusHttpClient
public async Task SearchRoomsAsync(RoomSearchRequest request)
{
- return await _modeusHttpClient.SearchRoomsAsync(request);
+ return await modeusHttpClient.SearchRoomsAsync(request);
}
public async Task GetScheduleAsync(ModeusScheduleRequest msr)
{
- return await _modeusHttpClient.GetScheduleAsync(msr);
+ return await modeusHttpClient.GetScheduleAsync(msr);
}
public async Task GetGuidAsync(string fullname)
{
- return await _modeusHttpClient.GetGuidAsync(fullname);
+ var searchPersonResponse = await modeusHttpClient.SearchPersonAsync(new ModeusSearchPersonRequest
+ { FullName = fullname, Page = 0, Size = 10, Sort = "+fullName" });
+ if (searchPersonResponse == null)
+ {
+ logger.LogErrorHere($"Не удалось получить ответ от Modeus при поиске пользователя. FullName={fullname}");
+ return null;
+ }
+
+ string? personId;
+ try
+ {
+ personId = searchPersonResponse.Embedded.Persons[0].Id;
+ return personId;
+ }
+ catch (Exception exception)
+ {
+ logger.LogWarningHere(
+ $"Не удалось получить идентификатор пользователя. FullName={fullname}. Ответ Modeus: {JsonSerializer.Serialize(searchPersonResponse, GlobalConsts.JsonSerializerOptions)}. Exception: {exception}");
+ }
+
+ return null;
}
-
+
public async Task> GetAttendeesAsync(Guid eventId)
{
- return await _modeusHttpClient.GetAttendeesAsync(eventId);
+ return await modeusHttpClient.GetAttendeesAsync(eventId);
}
#endregion