Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions ImovelStand.Api/Controllers/EspelhoController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ImovelStand.Application.Services;
using ImovelStand.Infrastructure.Persistence;

namespace ImovelStand.Api.Controllers;

[Authorize]
[ApiController]
[Route("api/empreendimentos/{empreendimentoId:int}/espelho")]
public class EspelhoController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly EspelhoPdfGenerator _generator;
private readonly ILogger<EspelhoController> _logger;

public EspelhoController(ApplicationDbContext context, EspelhoPdfGenerator generator, ILogger<EspelhoController> logger)
{
_context = context;
_generator = generator;
_logger = logger;
}

[HttpGet]
public async Task<IActionResult> GerarEspelho(
int empreendimentoId,
[FromQuery] TipoEspelho tipo = TipoEspelho.Comercial,
CancellationToken cancellationToken = default)
{
var empreendimento = await _context.Empreendimentos.AsNoTracking()
.FirstOrDefaultAsync(e => e.Id == empreendimentoId, cancellationToken);
if (empreendimento is null) return NotFound();

var torres = await _context.Torres.AsNoTracking()
.Where(t => t.EmpreendimentoId == empreendimentoId)
.OrderBy(t => t.Nome)
.ToListAsync(cancellationToken);

var tipologias = await _context.Tipologias.AsNoTracking()
.Where(t => t.EmpreendimentoId == empreendimentoId)
.ToListAsync(cancellationToken);

var torreIds = torres.Select(t => t.Id).ToArray();
var apartamentos = await _context.Apartamentos.AsNoTracking()
.Where(a => torreIds.Contains(a.TorreId))
.ToListAsync(cancellationToken);

var metadata = new EspelhoMetadata(
TenantNome: empreendimento.Nome,
GeradoPor: User.Identity?.Name ?? User.FindFirst(ClaimTypes.Email)?.Value ?? "desconhecido",
GeradoEm: DateTime.UtcNow);

var pdf = _generator.Gerar(tipo, empreendimento, torres, tipologias, apartamentos, metadata);

_logger.LogInformation("Espelho {Tipo} gerado para empreendimento {Id} por {User}",
tipo, empreendimentoId, metadata.GeradoPor);

var filename = $"espelho-{empreendimento.Slug}-{tipo.ToString().ToLowerInvariant()}-{DateTime.UtcNow:yyyyMMddHHmm}.pdf";
return File(pdf, "application/pdf", filename);
}
}
1 change: 1 addition & 0 deletions ImovelStand.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
builder.Services.AddSingleton<IFileStorage, MinioFileStorage>();
builder.Services.AddSingleton<ImageProcessor>();
builder.Services.AddSingleton<CalculadoraFinanceira>();
builder.Services.AddSingleton<EspelhoPdfGenerator>();

builder.Services.AddSingleton<HistoricoPrecoInterceptor>();
builder.Services.AddScoped<TenantAssignmentInterceptor>();
Expand Down
84 changes: 84 additions & 0 deletions ImovelStand.Tests/Services/EspelhoPdfGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using ImovelStand.Application.Services;
using ImovelStand.Domain.Entities;
using ImovelStand.Domain.Enums;

namespace ImovelStand.Tests.Services;

public class EspelhoPdfGeneratorTests
{
private readonly EspelhoPdfGenerator _sut = new();

private static (Empreendimento emp, List<Torre> torres, List<Tipologia> tipologias, List<Apartamento> apts) BuildCenario()
{
var emp = new Empreendimento { Id = 1, Nome = "Residencial Exemplo", Slug = "residencial-exemplo" };
var torres = new List<Torre>
{
new() { Id = 1, EmpreendimentoId = 1, Nome = "Torre A", Pavimentos = 4, ApartamentosPorPavimento = 2 }
};
var tipologias = new List<Tipologia>
{
new() { Id = 1, EmpreendimentoId = 1, Nome = "2Q", AreaPrivativa = 55, AreaTotal = 70, Quartos = 2, Banheiros = 1, PrecoBase = 350_000 },
new() { Id = 2, EmpreendimentoId = 1, Nome = "3Q", AreaPrivativa = 75, AreaTotal = 95, Quartos = 3, Banheiros = 2, PrecoBase = 520_000 }
};
var apts = new List<Apartamento>();
for (var pav = 1; pav <= 4; pav++)
{
for (var u = 1; u <= 2; u++)
{
var tipId = u == 1 ? 1 : 2;
apts.Add(new Apartamento
{
Id = apts.Count + 1,
TorreId = 1,
TipologiaId = tipId,
Numero = $"{pav:00}{u:00}",
Pavimento = pav,
PrecoAtual = tipId == 1 ? 350_000m : 520_000m,
Status = pav == 1 ? StatusApartamento.Vendido : StatusApartamento.Disponivel
});
}
}
return (emp, torres, tipologias, apts);
}

[Theory]
[InlineData(TipoEspelho.Comercial)]
[InlineData(TipoEspelho.PorTorre)]
[InlineData(TipoEspelho.Executivo)]
public void Gerar_ProduzPdfNaoVazio(TipoEspelho tipo)
{
var (emp, torres, tipologias, apts) = BuildCenario();
var metadata = new EspelhoMetadata("Tenant Demo", "user@test.com", new DateTime(2026, 4, 22, 10, 0, 0, DateTimeKind.Utc));

var pdf = _sut.Gerar(tipo, emp, torres, tipologias, apts, metadata);

Assert.NotNull(pdf);
Assert.True(pdf.Length > 1000, "PDF de verdade tem mais de 1KB.");
// Magic bytes: %PDF
Assert.Equal(0x25, pdf[0]);
Assert.Equal(0x50, pdf[1]);
Assert.Equal(0x44, pdf[2]);
Assert.Equal(0x46, pdf[3]);
}

[Fact]
public void Gerar_SemApartamentos_AindaGeraPdfValido()
{
var (emp, torres, tipologias, _) = BuildCenario();
var metadata = new EspelhoMetadata("Tenant Demo", "user", DateTime.UtcNow);

var pdf = _sut.Gerar(TipoEspelho.Executivo, emp, torres, tipologias, new List<Apartamento>(), metadata);

Assert.True(pdf.Length > 500);
}

[Fact]
public void Gerar_TipoInvalido_Lanca()
{
var (emp, torres, tipologias, apts) = BuildCenario();
var metadata = new EspelhoMetadata("x", "x", DateTime.UtcNow);

Assert.Throws<ArgumentOutOfRangeException>(() =>
_sut.Gerar((TipoEspelho)99, emp, torres, tipologias, apts, metadata));
}
}
1 change: 1 addition & 0 deletions src/ImovelStand.Application/ImovelStand.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
<PackageReference Include="QuestPDF" Version="2024.10.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
</ItemGroup>
Expand Down
Loading
Loading