Feature/routes export (#29)
Tests / backend-test (push) Successful in 6m7s
Tests / frontend-test (push) Successful in 1m29s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/29
This commit was merged in pull request #29.
This commit is contained in:
2026-10-14 05:06:58 +00:00
parent 05f73d356a
commit 0e6a7064d2
21 changed files with 832 additions and 28 deletions
@@ -15,7 +15,7 @@ namespace Tnsu.ApprovalRoute.Api.Controllers;
[ApiController]
[Authorize(Policy = AuthPolicies.Any)]
[Route("api/reports")]
public sealed class ReportsController(VacancyReportService vacancies) : ControllerBase
public sealed class ReportsController(VacancyReportService vacancies, RouteExportService routeExport) : ControllerBase
{
private const string XlsxContentType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
@@ -51,4 +51,27 @@ public sealed class ReportsController(VacancyReportService vacancies) : Controll
return File(file, XlsxContentType);
}
/// <summary>
/// Выгрузка маршрутов согласования выбранных типов заявок в xlsx (лист на тип).
/// POST — потому что в теле список id типов (см. RouteExportService).
/// </summary>
[HttpPost("routes")]
[Produces(XlsxContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Routes([FromBody] RouteExportRequest request, CancellationToken ct = default)
{
var result = await routeExport.BuildAsync(request.RequestTypeIds, ct);
if (result.IsFailed)
return BadRequest(new { errors = result.Errors.Select(e => e.Message) });
var file = result.Value;
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.SetHttpFileName(file.FileName);
Response.Headers.ContentDisposition = disposition.ToString();
return File(file.Content, XlsxContentType);
}
}
+4 -1
View File
@@ -53,7 +53,10 @@ builder.Services.AddCors(options =>
options.AddPolicy(CorsPolicyName, policy => policy
.WithOrigins(corsAllowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod());
.AllowAnyMethod()
// Без этого браузер не отдаёт скрипту Content-Disposition кросс-доменного ответа,
// а из него фронт берёт имя скачиваемого файла (выгрузка маршрутов).
.WithExposedHeaders("Content-Disposition"));
});
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddApprovalRouteInfrastructure(builder.Configuration);
@@ -3,6 +3,9 @@
<ProjectReference Include="..\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj" />
<ProjectReference Include="..\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.*.json" DependentUpon="appsettings.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extensions.Hosting.AsyncInitialization" />
<PackageReference Include="Hangfire.AspNetCore" />