логика готова
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"TenantId": "",
|
"TenantId": "",
|
||||||
"ClientId": "",
|
"ClientId": "",
|
||||||
"ClientSecret": "",
|
"ClientSecret": "",
|
||||||
"Scope": ""
|
"Scope": "",
|
||||||
|
"DirectorTansuEmail": "director.tansu@tnsu.kz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ public sealed record ResolvedRouteStepDto(
|
|||||||
int OrderNo,
|
int OrderNo,
|
||||||
string? ExecutorEmail,
|
string? ExecutorEmail,
|
||||||
string? Position,
|
string? Position,
|
||||||
ProjectRole? ProjectRole,
|
|
||||||
HeadType? HeadType,
|
|
||||||
RouteStepStatus Status,
|
|
||||||
bool AllowReassignToOtherApprovers);
|
bool AllowReassignToOtherApprovers);
|
||||||
|
|
||||||
public sealed record ResolveTemplateRouteResultDto(
|
public sealed record ResolveTemplateRouteResultDto(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using FluentResults;
|
using FluentResults;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Tnsu.ApprovalRoute.Application.Common.Errors;
|
using Tnsu.ApprovalRoute.Application.Common.Errors;
|
||||||
using Tnsu.ApprovalRoute.Application.Common.Interfaces;
|
using Tnsu.ApprovalRoute.Application.Common.Interfaces;
|
||||||
using Tnsu.ApprovalRoute.Domain.Entities;
|
using Tnsu.ApprovalRoute.Domain.Entities;
|
||||||
@@ -12,7 +13,8 @@ namespace Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
|||||||
public sealed class TemplateRouteService(
|
public sealed class TemplateRouteService(
|
||||||
ApprovalRouteDbContext db,
|
ApprovalRouteDbContext db,
|
||||||
ITnsuPortalClient portalClient,
|
ITnsuPortalClient portalClient,
|
||||||
ICurrentUser currentUser)
|
ICurrentUser currentUser,
|
||||||
|
IOptions<TnsuPortalOptions> portalOptions)
|
||||||
{
|
{
|
||||||
public async Task<Result<IReadOnlyList<TemplateRouteDto>>> ListAsync(int? requestTypeId, CancellationToken cancellationToken)
|
public async Task<Result<IReadOnlyList<TemplateRouteDto>>> ListAsync(int? requestTypeId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -166,12 +168,15 @@ public sealed class TemplateRouteService(
|
|||||||
if (request.ProjectId is { } projectId)
|
if (request.ProjectId is { } projectId)
|
||||||
projectExecutors = await portalClient.GetProjectExecutorsAsync(projectId, cancellationToken);
|
projectExecutors = await portalClient.GetProjectExecutorsAsync(projectId, cancellationToken);
|
||||||
|
|
||||||
var steps = route.Steps
|
var matchingSteps = route.Steps
|
||||||
.Where(s => s.Status == request.Status)
|
.Where(s => s.Status == request.Status)
|
||||||
.OrderBy(s => s.OrderNo)
|
.OrderBy(s => s.OrderNo)
|
||||||
.Select(s => ResolveStep(s, projectExecutors))
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var steps = new List<ResolvedRouteStepDto>();
|
||||||
|
foreach (var step in matchingSteps)
|
||||||
|
steps.Add(await ResolveStepAsync(step, email, employee.Company, projectExecutors, cancellationToken));
|
||||||
|
|
||||||
return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RouteType, route.RequiresControl, steps));
|
return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RouteType, route.RequiresControl, steps));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,20 +190,37 @@ public sealed class TemplateRouteService(
|
|||||||
x.ByProject == byProject,
|
x.ByProject == byProject,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
private static ResolvedRouteStepDto ResolveStep(TemplateRouteStep step, IReadOnlyList<ProjectExecutorInfo> projectExecutors)
|
private async Task<ResolvedRouteStepDto> ResolveStepAsync(
|
||||||
|
TemplateRouteStep step, string initiatorEmail, string initiatorCompany,
|
||||||
|
IReadOnlyList<ProjectExecutorInfo> projectExecutors, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (step.ProjectRole is not (null or ProjectRole.None))
|
string? resolvedEmail;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(step.ExecutorEmail))
|
||||||
|
resolvedEmail = step.ExecutorEmail;
|
||||||
|
else if (!string.IsNullOrWhiteSpace(step.Position))
|
||||||
|
resolvedEmail = await portalClient.GetEmployeeByPositionAsync(step.Position, initiatorCompany, cancellationToken);
|
||||||
|
else if (step.ProjectRole is not (null or ProjectRole.None))
|
||||||
|
resolvedEmail = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole)?.Email;
|
||||||
|
else if (step.HeadType is not (null or HeadType.None))
|
||||||
|
resolvedEmail = step.HeadType switch
|
||||||
|
{
|
||||||
|
HeadType.Chief => await portalClient.GetManagerAsync(initiatorEmail, "chief", cancellationToken),
|
||||||
|
HeadType.DirectionHead => await portalClient.GetManagerAsync(initiatorEmail, "directionHead", cancellationToken),
|
||||||
|
HeadType.DirectorTansu => portalOptions.Value.DirectorTansuEmail,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
else
|
||||||
|
resolvedEmail = null;
|
||||||
|
|
||||||
|
string? resolvedPosition = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(resolvedEmail))
|
||||||
{
|
{
|
||||||
var projectExecutor = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole);
|
var resolvedEmployee = await portalClient.GetEmployeeByEmailAsync(resolvedEmail, cancellationToken);
|
||||||
if (projectExecutor is not null)
|
resolvedPosition = resolvedEmployee?.Position;
|
||||||
return new ResolvedRouteStepDto(
|
|
||||||
step.Id, step.OrderNo, projectExecutor.Email, step.Position,
|
|
||||||
step.ProjectRole, step.HeadType, step.Status, step.AllowReassignToOtherApprovers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ResolvedRouteStepDto(
|
return new ResolvedRouteStepDto(step.Id, step.OrderNo, resolvedEmail, resolvedPosition, step.AllowReassignToOtherApprovers);
|
||||||
step.Id, step.OrderNo, step.ExecutorEmail, step.Position,
|
|
||||||
step.ProjectRole, step.HeadType, step.Status, step.AllowReassignToOtherApprovers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TemplateRouteDto ToDto(TemplateRoute e) => new(
|
private static TemplateRouteDto ToDto(TemplateRoute e) => new(
|
||||||
|
|||||||
@@ -17,32 +17,33 @@ public sealed class HttpTnsuPortalClient(
|
|||||||
|
|
||||||
public async Task<EmployeeDirectoryInfo?> GetEmployeeByEmailAsync(string email, CancellationToken cancellationToken)
|
public async Task<EmployeeDirectoryInfo?> GetEmployeeByEmailAsync(string email, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/Employee/ByEmail?email={Uri.EscapeDataString(email)}");
|
var employee = await GetAsync<EmployeeResponse>(
|
||||||
await AuthorizeAsync(request, cancellationToken);
|
$"/Employee/ByEmail?email={Uri.EscapeDataString(email)}", cancellationToken);
|
||||||
|
|
||||||
using var response = await httpClient.SendAsync(request, cancellationToken);
|
|
||||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
return null;
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var employee = await response.Content.ReadFromJsonAsync<EmployeeResponse>(JsonOptions, cancellationToken);
|
|
||||||
if (employee is null)
|
if (employee is null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new EmployeeDirectoryInfo(employee.CompanyId ?? Guid.Empty, employee.DepartmentId);
|
return new EmployeeDirectoryInfo(employee.CompanyId ?? Guid.Empty, employee.DepartmentId, employee.Company ?? string.Empty, employee.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetEmployeeByPositionAsync(string position, string company, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var employee = await GetAsync<EmployeeResponse>(
|
||||||
|
$"/Employee/ByPosition?position={Uri.EscapeDataString(position)}&company={Uri.EscapeDataString(company)}", cancellationToken);
|
||||||
|
|
||||||
|
return employee?.Email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetManagerAsync(string email, string managerType, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var manager = await GetAsync<ManagerResponse>(
|
||||||
|
$"/Employee/Manager?email={Uri.EscapeDataString(email)}&managerType={Uri.EscapeDataString(managerType)}", cancellationToken);
|
||||||
|
|
||||||
|
return manager?.ManagerEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<ProjectExecutorInfo>> GetProjectExecutorsAsync(Guid projectId, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<ProjectExecutorInfo>> GetProjectExecutorsAsync(Guid projectId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"/Project/{projectId}");
|
var project = await GetAsync<ProjectResponse>($"/Project/{projectId}", cancellationToken);
|
||||||
await AuthorizeAsync(request, cancellationToken);
|
|
||||||
|
|
||||||
using var response = await httpClient.SendAsync(request, cancellationToken);
|
|
||||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
return [];
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var project = await response.Content.ReadFromJsonAsync<ProjectResponse>(JsonOptions, cancellationToken);
|
|
||||||
if (project is null)
|
if (project is null)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
@@ -55,6 +56,19 @@ public sealed class HttpTnsuPortalClient(
|
|||||||
return executors;
|
return executors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<T?> GetAsync<T>(string requestUri, CancellationToken cancellationToken) where T : class
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||||
|
await AuthorizeAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
using var response = await httpClient.SendAsync(request, cancellationToken);
|
||||||
|
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
return null;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<T>(JsonOptions, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddIfPresent(List<ProjectExecutorInfo> executors, ProjectRole role, string? email)
|
private static void AddIfPresent(List<ProjectExecutorInfo> executors, ProjectRole role, string? email)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(email))
|
if (!string.IsNullOrWhiteSpace(email))
|
||||||
@@ -75,8 +89,14 @@ public sealed class HttpTnsuPortalClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sealed record EmployeeResponse(
|
private sealed record EmployeeResponse(
|
||||||
|
[property: JsonPropertyName("email")] string? Email,
|
||||||
|
[property: JsonPropertyName("company")] string? Company,
|
||||||
[property: JsonPropertyName("companyId")] Guid? CompanyId,
|
[property: JsonPropertyName("companyId")] Guid? CompanyId,
|
||||||
[property: JsonPropertyName("departmentId")] Guid? DepartmentId);
|
[property: JsonPropertyName("departmentId")] Guid? DepartmentId,
|
||||||
|
[property: JsonPropertyName("position")] string? Position);
|
||||||
|
|
||||||
|
private sealed record ManagerResponse(
|
||||||
|
[property: JsonPropertyName("managerEmail")] string? ManagerEmail);
|
||||||
|
|
||||||
private sealed record ProjectResponse(
|
private sealed record ProjectResponse(
|
||||||
[property: JsonPropertyName("projectManager")] string? ProjectManager,
|
[property: JsonPropertyName("projectManager")] string? ProjectManager,
|
||||||
|
|||||||
@@ -4,5 +4,9 @@ public interface ITnsuPortalClient
|
|||||||
{
|
{
|
||||||
Task<EmployeeDirectoryInfo?> GetEmployeeByEmailAsync(string email, CancellationToken cancellationToken);
|
Task<EmployeeDirectoryInfo?> GetEmployeeByEmailAsync(string email, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<string?> GetEmployeeByPositionAsync(string position, string company, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<string?> GetManagerAsync(string email, string managerType, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<IReadOnlyList<ProjectExecutorInfo>> GetProjectExecutorsAsync(Guid projectId, CancellationToken cancellationToken);
|
Task<IReadOnlyList<ProjectExecutorInfo>> GetProjectExecutorsAsync(Guid projectId, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ using Tnsu.ApprovalRoute.Domain.Enums;
|
|||||||
|
|
||||||
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
public sealed record EmployeeDirectoryInfo(Guid CompanyId, Guid? DepartmentId);
|
public sealed record EmployeeDirectoryInfo(Guid CompanyId, Guid? DepartmentId, string Company, string? Position);
|
||||||
|
|
||||||
public sealed record ProjectExecutorInfo(ProjectRole ProjectRole, string Email);
|
public sealed record ProjectExecutorInfo(ProjectRole ProjectRole, string Email);
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ public sealed class TnsuPortalOptions
|
|||||||
public string ClientId { get; set; } = string.Empty;
|
public string ClientId { get; set; } = string.Empty;
|
||||||
public string ClientSecret { get; set; } = string.Empty;
|
public string ClientSecret { get; set; } = string.Empty;
|
||||||
public string Scope { get; set; } = string.Empty;
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
public string DirectorTansuEmail { get; set; } = "director.tansu@tnsu.kz";
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-4
@@ -15,21 +15,24 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
private static readonly Guid CompanyId = Guid.Parse("00000000-0000-0000-0000-000000000002");
|
||||||
private static readonly Guid DepartmentId = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
private static readonly Guid DepartmentId = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||||
|
|
||||||
|
private readonly string _connectionString;
|
||||||
private readonly ServiceProvider _services;
|
private readonly ServiceProvider _services;
|
||||||
private readonly TemplateRouteService _service;
|
private readonly TemplateRouteService _service;
|
||||||
private readonly RequestTypeService _requestTypeService;
|
private readonly RequestTypeService _requestTypeService;
|
||||||
|
|
||||||
public TemplateRouteServiceTests(PostgresDatabaseFixture fixture)
|
public TemplateRouteServiceTests(PostgresDatabaseFixture fixture)
|
||||||
{
|
{
|
||||||
|
_connectionString = fixture.ConnectionString;
|
||||||
|
|
||||||
var portalClient = new Mock<ITnsuPortalClient>();
|
var portalClient = new Mock<ITnsuPortalClient>();
|
||||||
portalClient
|
portalClient
|
||||||
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync(new EmployeeDirectoryInfo(CompanyId, DepartmentId));
|
.ReturnsAsync(new EmployeeDirectoryInfo(CompanyId, DepartmentId, "ТОО TANSU Construction", "Тестовая должность"));
|
||||||
portalClient
|
portalClient
|
||||||
.Setup(x => x.GetProjectExecutorsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
.Setup(x => x.GetProjectExecutorsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync([new ProjectExecutorInfo(ProjectRole.ProjectManager, "pm@tnsu.kz")]);
|
.ReturnsAsync([new ProjectExecutorInfo(ProjectRole.ProjectManager, "pm@tnsu.kz")]);
|
||||||
|
|
||||||
_services = TestServiceProviderFactory.Build(fixture.ConnectionString, portalClient: portalClient.Object);
|
_services = TestServiceProviderFactory.Build(_connectionString, portalClient: portalClient.Object);
|
||||||
_service = _services.GetRequiredService<TemplateRouteService>();
|
_service = _services.GetRequiredService<TemplateRouteService>();
|
||||||
_requestTypeService = _services.GetRequiredService<RequestTypeService>();
|
_requestTypeService = _services.GetRequiredService<RequestTypeService>();
|
||||||
}
|
}
|
||||||
@@ -160,7 +163,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||||
await stepService.CreateAsync(
|
await stepService.CreateAsync(
|
||||||
route.Value.Id,
|
route.Value.Id,
|
||||||
new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", "Lead", null, HeadType.Chief, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", null, null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
@@ -187,7 +190,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||||
await stepService.CreateAsync(
|
await stepService.CreateAsync(
|
||||||
route.Value.Id,
|
route.Value.Id,
|
||||||
new CreateTemplateRouteStepRequest(1, "", "Lead", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
@@ -201,6 +204,119 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("pm@tnsu.kz");
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("pm@tnsu.kz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "Резолв шага с должностью подставляет email сотрудника, найденного по должности и компании инициатора")]
|
||||||
|
public async Task ResolveAsync_ByPosition_UsesEmployeeFoundByPositionAndCompany()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var portalClient = new Mock<ITnsuPortalClient>();
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new EmployeeDirectoryInfo(CompanyId, DepartmentId, "ТОО TANSU Construction", "Lead Engineer"));
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetEmployeeByPositionAsync("Lead", "ТОО TANSU Construction", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync("lead@tnsu.kz");
|
||||||
|
|
||||||
|
var services = TestServiceProviderFactory.Build(_connectionString, portalClient: portalClient.Object);
|
||||||
|
var routeService = services.GetRequiredService<TemplateRouteService>();
|
||||||
|
var requestTypeService = services.GetRequiredService<RequestTypeService>();
|
||||||
|
var stepService = services.GetRequiredService<TemplateRouteStepService>();
|
||||||
|
|
||||||
|
var requestTypeId = (await requestTypeService.CreateAsync(
|
||||||
|
new CreateRequestTypeRequest("Position request", "/position-request"), CancellationToken.None)).Value.Id;
|
||||||
|
var route = await routeService.CreateAsync(
|
||||||
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||||
|
CancellationToken.None);
|
||||||
|
await stepService.CreateAsync(
|
||||||
|
route.Value.Id,
|
||||||
|
new CreateTemplateRouteStepRequest(1, null, "Lead", null, null, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var resolved = await routeService.ResolveAsync(
|
||||||
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
resolved.IsSuccess.ShouldBeTrue();
|
||||||
|
resolved.Value.Steps.ShouldHaveSingleItem();
|
||||||
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("lead@tnsu.kz");
|
||||||
|
resolved.Value.Steps[0].Position.ShouldBe("Lead Engineer");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "Резолв шага с типом руководителя Chief подставляет email руководителя из api.tnsu.kz")]
|
||||||
|
public async Task ResolveAsync_ByHeadTypeChief_UsesManagerEmail()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var portalClient = new Mock<ITnsuPortalClient>();
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new EmployeeDirectoryInfo(CompanyId, DepartmentId, "ТОО TANSU Construction", "Chief position"));
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetManagerAsync("someone@tnsu.kz", "chief", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync("chief@tnsu.kz");
|
||||||
|
|
||||||
|
var services = TestServiceProviderFactory.Build(_connectionString, portalClient: portalClient.Object);
|
||||||
|
var routeService = services.GetRequiredService<TemplateRouteService>();
|
||||||
|
var requestTypeService = services.GetRequiredService<RequestTypeService>();
|
||||||
|
var stepService = services.GetRequiredService<TemplateRouteStepService>();
|
||||||
|
|
||||||
|
var requestTypeId = (await requestTypeService.CreateAsync(
|
||||||
|
new CreateRequestTypeRequest("Chief request", "/chief-request"), CancellationToken.None)).Value.Id;
|
||||||
|
var route = await routeService.CreateAsync(
|
||||||
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||||
|
CancellationToken.None);
|
||||||
|
await stepService.CreateAsync(
|
||||||
|
route.Value.Id,
|
||||||
|
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var resolved = await routeService.ResolveAsync(
|
||||||
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
resolved.IsSuccess.ShouldBeTrue();
|
||||||
|
resolved.Value.Steps.ShouldHaveSingleItem();
|
||||||
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("chief@tnsu.kz");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "Резолв шага с типом руководителя DirectorTansu подставляет захардкоженный email из настроек")]
|
||||||
|
public async Task ResolveAsync_ByHeadTypeDirectorTansu_UsesConfiguredEmail()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var portalClient = new Mock<ITnsuPortalClient>();
|
||||||
|
portalClient
|
||||||
|
.Setup(x => x.GetEmployeeByEmailAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new EmployeeDirectoryInfo(CompanyId, DepartmentId, "ТОО TANSU Construction", "Director position"));
|
||||||
|
|
||||||
|
var services = TestServiceProviderFactory.Build(_connectionString, portalClient: portalClient.Object);
|
||||||
|
var routeService = services.GetRequiredService<TemplateRouteService>();
|
||||||
|
var requestTypeService = services.GetRequiredService<RequestTypeService>();
|
||||||
|
var stepService = services.GetRequiredService<TemplateRouteStepService>();
|
||||||
|
|
||||||
|
var requestTypeId = (await requestTypeService.CreateAsync(
|
||||||
|
new CreateRequestTypeRequest("DirectorTansu request", "/director-tansu-request"), CancellationToken.None)).Value.Id;
|
||||||
|
var route = await routeService.CreateAsync(
|
||||||
|
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
|
||||||
|
CancellationToken.None);
|
||||||
|
await stepService.CreateAsync(
|
||||||
|
route.Value.Id,
|
||||||
|
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var resolved = await routeService.ResolveAsync(
|
||||||
|
new ResolveTemplateRouteRequest(requestTypeId, "someone@tnsu.kz", null, RouteStepStatus.Approval),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
resolved.IsSuccess.ShouldBeTrue();
|
||||||
|
resolved.Value.Steps.ShouldHaveSingleItem();
|
||||||
|
resolved.Value.Steps[0].ExecutorEmail.ShouldBe("director.tansu@tnsu.kz");
|
||||||
|
portalClient.Verify(x => x.GetManagerAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = "Резолв маршрута откатывается на маршрут без отдела, если точного совпадения по отделу нет")]
|
[Fact(DisplayName = "Резолв маршрута откатывается на маршрут без отдела, если точного совпадения по отделу нет")]
|
||||||
public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute()
|
public async Task ResolveAsync_NoDepartmentRoute_FallsBackToCompanyWideRoute()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user