feat: complete all 17 questpdf crystal report migrations

- Anexo, ContratoBlack, Presupuesto reports (ReportService)
- CAEMP (ContratoAbiertoSinSence), CAEMPSNC (ContratoAbiertoConSence)
- CC_EMPCSD (ContratoCerradoConDescuento), PropuestaComercial
- All endpoints added to ReportController + EmpresaReportController
- Fase 1 now 100% complete: 21 controllers, 8 services, 7 repos, 17 reports
This commit is contained in:
2026-07-08 10:28:47 -04:00
parent c1e35445f6
commit 202a59326b
6 changed files with 363 additions and 45 deletions
@@ -18,29 +18,33 @@ public class EmpresaReportController : ControllerBase
[HttpGet("contrato-abierto/{id}")]
public async Task<IActionResult> ContratoAbiertoPdf(int id)
{
var pdf = await _empresaReportService.ContratoAbiertoPdfAsync(id);
return File(pdf, "application/pdf", $"contrato_abierto_{id}.pdf");
}
=> File(await _empresaReportService.ContratoAbiertoPdfAsync(id), "application/pdf", $"contrato_abierto_{id}.pdf");
[HttpGet("contrato-abierto-sin-sence/{id}")]
public async Task<IActionResult> ContratoAbiertoSinSencePdf(int id)
=> File(await _empresaReportService.ContratoAbiertoSinSencePdfAsync(id), "application/pdf", $"caemp_{id}.pdf");
[HttpGet("contrato-abierto-con-sence/{id}")]
public async Task<IActionResult> ContratoAbiertoConSencePdf(int id)
=> File(await _empresaReportService.ContratoAbiertoConSencePdfAsync(id), "application/pdf", $"caempsnc_{id}.pdf");
[HttpGet("contrato-cerrado/{prop}/{cont}")]
public async Task<IActionResult> ContratoCerradoPdf(int prop, int cont)
{
var pdf = await _empresaReportService.ContratoCerradoPdfAsync(prop, cont);
return File(pdf, "application/pdf", $"contrato_cerrado_{cont}.pdf");
}
=> File(await _empresaReportService.ContratoCerradoPdfAsync(prop, cont), "application/pdf", $"contrato_cerrado_{cont}.pdf");
[HttpGet("contrato-cerrado-descuento/{prop}/{cont}")]
public async Task<IActionResult> ContratoCerradoDescuentoPdf(int prop, int cont)
=> File(await _empresaReportService.ContratoCerradoConDescuentoPdfAsync(prop, cont), "application/pdf", $"cc_empcsd_{cont}.pdf");
[HttpGet("cotizacion-curso-cerrado/{prop}")]
public async Task<IActionResult> CotizacionCursoCerradoPdf(int prop)
{
var pdf = await _empresaReportService.CotizacionCursoCerradoPdfAsync(prop);
return File(pdf, "application/pdf", $"cotizacion_cc_{prop}.pdf");
}
=> File(await _empresaReportService.CotizacionCursoCerradoPdfAsync(prop), "application/pdf", $"cotizacion_cc_{prop}.pdf");
[HttpGet("cotizacion-plan-central/{id}")]
public async Task<IActionResult> CotizacionPlanCentralPdf(int id)
{
var pdf = await _empresaReportService.CotizacionPlanCentralPdfAsync(id);
return File(pdf, "application/pdf", $"cotizacion_pc_{id}.pdf");
}
=> File(await _empresaReportService.CotizacionPlanCentralPdfAsync(id), "application/pdf", $"cotizacion_pc_{id}.pdf");
[HttpGet("propuesta-comercial/{prop}/{cotz}/{cont}")]
public async Task<IActionResult> PropuestaComercialPdf(int prop, int cotz, int cont)
=> File(await _empresaReportService.PropuestaComercialPdfAsync(prop, cotz, cont), "application/pdf", $"propuesta_{prop}.pdf");
}
@@ -18,30 +18,30 @@ public class ReportController : ControllerBase
[HttpGet("contrato/{id}")]
public async Task<IActionResult> ContratoPdf(int id)
{
var pdf = await _reportService.GenerarContratoPdfAsync(id);
return File(pdf, "application/pdf", $"contrato_{id}.pdf");
}
=> File(await _reportService.GenerarContratoPdfAsync(id), "application/pdf", $"contrato_{id}.pdf");
[HttpGet("contrato-black/{id}")]
public async Task<IActionResult> ContratoBlackPdf(int id)
=> File(await _reportService.GenerarContratoBlackPdfAsync(id), "application/pdf", $"contrato_black_{id}.pdf");
[HttpGet("cotizacion/{id}")]
public async Task<IActionResult> CotizacionPdf(int id)
{
var pdf = await _reportService.GenerarCotizacionPdfAsync(id);
return File(pdf, "application/pdf", $"cotizacion_{id}.pdf");
}
=> File(await _reportService.GenerarCotizacionPdfAsync(id), "application/pdf", $"cotizacion_{id}.pdf");
[HttpGet("anexo/{contratoId}/{anexoId}")]
public async Task<IActionResult> AnexoPdf(int contratoId, int anexoId)
=> File(await _reportService.GenerarAnexoPdfAsync(contratoId, anexoId), "application/pdf", $"anexo_{anexoId}.pdf");
[HttpGet("presupuesto/{id}")]
public async Task<IActionResult> PresupuestoPdf(int id)
=> File(await _reportService.GenerarPresupuestoPdfAsync(id), "application/pdf", $"presupuesto_{id}.pdf");
[HttpPost("arqueo")]
public async Task<IActionResult> ArqueoPdf(
[FromQuery] string usuario,
[FromQuery] DateTime fecha,
[FromQuery] string usuario, [FromQuery] DateTime fecha,
[FromBody] List<Dictionary<string, object>> ingresos,
[FromQuery] int totalCredito,
[FromQuery] int totalDebito,
[FromQuery] int totalIntl,
[FromQuery] int totalEstado,
[FromQuery] int totalGeneral)
{
var pdf = await _reportService.GenerarArqueoPdfAsync(usuario, fecha, ingresos, totalCredito, totalDebito, totalIntl, totalEstado, totalGeneral);
return File(pdf, "application/pdf", $"arqueo_{fecha:yyyyMMdd}.pdf");
}
[FromQuery] int totalCredito, [FromQuery] int totalDebito,
[FromQuery] int totalIntl, [FromQuery] int totalEstado, [FromQuery] int totalGeneral)
=> File(await _reportService.GenerarArqueoPdfAsync(usuario, fecha, ingresos, totalCredito, totalDebito, totalIntl, totalEstado, totalGeneral),
"application/pdf", $"arqueo_{fecha:yyyyMMdd}.pdf");
}
+2 -1
View File
@@ -45,7 +45,8 @@ builder.Services.AddScoped<AlumnoService>(sp =>
builder.Services.AddScoped<ArqueoService>(sp =>
new ArqueoService(connectionString));
builder.Services.AddScoped<InformeService>();
builder.Services.AddScoped<ReportService>();
builder.Services.AddScoped<ReportService>(sp =>
new ReportService(sp.GetRequiredService<ContratoService>(), sp.GetRequiredService<CotizacionService>(), connectionString));
builder.Services.AddScoped<EmpresaReportService>(sp =>
new EmpresaReportService(sp.GetRequiredService<IInformeRepository>(), connectionString));
@@ -287,4 +287,145 @@ public class EmpresaReportService
});
});
}
// Variantes CAEMP/CAEMPSNC/CAEMPV2 — mismo SP, layout con/sin SENCE
public async Task<byte[]> ContratoAbiertoSinSencePdfAsync(int contratoId)
{
var dt01 = await ContratoCrysAsync("CONTRATOREIMPRESION", contratoId);
var dt02 = await ContratoCrysAsync("DETALLECURSO", contratoId);
var header = dt01.FirstOrDefault();
if (header == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4); page.Margin(57); page.DefaultTextStyle(x => x.FontSize(9));
page.Header().Element(c => ComposeEmpHeader(c, GetString(header, "NombreEmp"), contratoId.ToString()));
page.Content().Column(col =>
{
col.Item().PaddingTop(15).Text("CONTRATO SIN SENCE").FontSize(12).Bold();
col.Item().PaddingTop(10).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(30); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(70); c.ConstantColumn(70); });
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Duración"); h.Cell().Text("Inicio"); h.Cell().Text("Término"); });
int i = 1;
foreach (var c in dt02)
{
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "Curso"));
table.Cell().Text(GetString(c, "Duracion")); table.Cell().Text(GetString(c, "FechaInicio")); table.Cell().Text(GetString(c, "FechaTermino"));
}
});
});
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
});
}).GeneratePdf();
}
public async Task<byte[]> ContratoAbiertoConSencePdfAsync(int contratoId)
{
var dt01 = await ContratoCrysAsync("CONTRATOREIMPRESION", contratoId);
var dt02 = await ContratoCrysAsync("DETALLECURSO", contratoId);
var header = dt01.FirstOrDefault();
if (header == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4); page.Margin(57); page.DefaultTextStyle(x => x.FontSize(9));
page.Header().Element(c => ComposeEmpHeader(c, GetString(header, "NombreEmp"), contratoId.ToString()));
page.Content().Column(col =>
{
col.Item().PaddingTop(15).Text("CONTRATO CON SENCE").FontSize(12).Bold();
col.Item().PaddingTop(10).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(30); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(70); c.ConstantColumn(70); c.ConstantColumn(60); });
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Sence"); h.Cell().Text("Duración"); h.Cell().Text("Inicio"); h.Cell().Text("Término"); });
int i = 1;
foreach (var c in dt02)
{
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "Curso"));
table.Cell().Text(GetString(c, "Sence")); table.Cell().Text(GetString(c, "Duracion"));
table.Cell().Text(GetString(c, "FechaInicio")); table.Cell().Text(GetString(c, "FechaTermino"));
}
});
});
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
});
}).GeneratePdf();
}
public async Task<byte[]> ContratoCerradoGeneralPdfAsync(int propuestaId, int contratoId)
=> await ContratoCerradoPdfAsync(propuestaId, contratoId);
public async Task<byte[]> ContratoCerradoConDescuentoPdfAsync(int propuestaId, int contratoId)
{
var header = (await _informeRepository.EjecutarCrystalSpAsync("PROPUESTA", propuestaId, 0, contratoId)).FirstOrDefault();
var cursos = await _informeRepository.EjecutarCrystalSpAsync("CONTRATOCURSO", propuestaId, 0, contratoId);
if (header == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4); page.Margin(57); page.DefaultTextStyle(x => x.FontSize(9));
page.Header().Element(c => ComposeContratoCerradoHeader(c, GetString(header, "EMPRESA"), contratoId.ToString()));
page.Content().Column(col =>
{
col.Item().PaddingTop(15).Text("CONTRATO CON DESCUENTO").FontSize(12).Bold();
col.Item().PaddingTop(10).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(110); c.RelativeColumn(); });
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(header, "TOTAL"));
table.Cell().Text("Descuento:").Bold(); table.Cell().Text("$" + GetString(header, "DESCUENTO"));
table.Cell().Text("Total c/Desc:").Bold(); table.Cell().Text("$" + GetString(header, "TOTAL CON DESCUENTO"));
});
col.Item().PaddingTop(15).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(25); c.RelativeColumn(); c.ConstantColumn(60); c.ConstantColumn(60); });
table.Header(h => { h.Cell().Text("#"); h.Cell().Text("Curso"); h.Cell().Text("Valor"); h.Cell().Text("Dcto Apl."); });
int i = 1;
foreach (var c in cursos)
{
table.Cell().Text((i++).ToString()); table.Cell().Text(GetString(c, "CURSO"));
table.Cell().Text("$" + GetString(c, "ValorGrupal")); table.Cell().Text("$" + GetString(c, "ValorGrupalDesctoAplicado"));
}
});
});
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
});
}).GeneratePdf();
}
public async Task<byte[]> PropuestaComercialPdfAsync(int propuestaId, int cotizacionId, int contratoId)
{
var data = await _informeRepository.EjecutarCrystalSpAsync("PROPUESTA", propuestaId, cotizacionId, contratoId);
var header = data.FirstOrDefault();
if (header == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4); page.Margin(57);
page.DefaultTextStyle(x => x.FontSize(12));
page.Content().Column(col =>
{
col.Item().PaddingTop(40).AlignCenter().Text("PROPUESTA COMERCIAL").FontSize(20).Bold();
col.Item().PaddingTop(30).AlignCenter().Text("Instituto Chileno Norteamericano").FontSize(16);
col.Item().PaddingTop(40).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
table.Cell().Text("Empresa:").Bold(); table.Cell().Text(GetString(header, "EMPRESA"));
table.Cell().Text("RUT:").Bold(); table.Cell().Text(GetString(header, "RUT"));
table.Cell().Text("Vendedor:").Bold(); table.Cell().Text(GetString(header, "VENDEDOR"));
table.Cell().Text("Total:").Bold(); table.Cell().Text("$" + GetString(header, "TOTAL"));
});
col.Item().PaddingTop(20).AlignCenter().Text("Documento generado electrónicamente").FontSize(9).Italic();
});
});
}).GeneratePdf();
}
}
+172 -1
View File
@@ -1,3 +1,5 @@
using Dapper;
using Npgsql;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -8,11 +10,20 @@ public class ReportService
{
private readonly ContratoService _contratoService;
private readonly CotizacionService _cotizacionService;
private readonly string _connectionString;
public ReportService(ContratoService contratoService, CotizacionService cotizacionService)
public ReportService(ContratoService contratoService, CotizacionService cotizacionService, string connectionString)
{
_contratoService = contratoService;
_cotizacionService = cotizacionService;
_connectionString = connectionString;
}
private async Task<IEnumerable<Dictionary<string, object>>> QuerySpAsync(string sp, object parameters)
{
using var connection = new NpgsqlConnection(_connectionString);
var rows = await connection.QueryAsync(sp, parameters, commandType: System.Data.CommandType.StoredProcedure);
return rows.Select(r => (Dictionary<string, object>)(IDictionary<string, object>)r!);
}
public async Task<byte[]> GenerarContratoPdfAsync(int contratoId)
@@ -287,4 +298,164 @@ public class ReportService
});
});
}
public async Task<byte[]> GenerarContratoBlackPdfAsync(int contratoId)
{
var data = await _contratoService.PdfContratoAsync(contratoId);
var jornadas = await _contratoService.PdfContratoJornadasAsync(contratoId);
var programas = await _contratoService.PdfContratoProgramasCursosAsync(contratoId);
var sedes = await _contratoService.PdfContratoSedesAsync(contratoId);
var row = data.FirstOrDefault();
if (row == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(57);
page.DefaultTextStyle(x => x.FontSize(10).FontColor(Colors.White));
page.PageColor(Colors.Black);
page.Header().Element(c => ComposeContratoBlackHeader(c, contratoId.ToString()));
page.Content().Element(c => ComposeContratoBlackContent(c, row, programas, jornadas, sedes));
page.Footer().AlignCenter().Text(t => { t.Span("Página ").FontColor(Colors.White); t.CurrentPageNumber().FontColor(Colors.White); });
});
}).GeneratePdf();
}
private void ComposeContratoBlackHeader(IContainer container, string numero)
{
container.Row(row =>
{
row.RelativeItem();
row.ConstantItem(120).AlignRight().Column(col =>
{
col.Item().Text($"Contrato N° {numero}").FontSize(16).Bold().FontColor(Colors.White);
});
});
}
private void ComposeContratoBlackContent(IContainer container, Dictionary<string, object> row,
IEnumerable<Dictionary<string, object>> programas, IEnumerable<Dictionary<string, object>> jornadas,
IEnumerable<Dictionary<string, object>> sedes)
{
container.Column(col =>
{
col.Item().PaddingTop(20).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
table.Cell().Text("Alumno:").Bold().FontColor(Colors.White);
table.Cell().Text(row.GetValueOrDefault("Alumno")?.ToString() ?? "").FontColor(Colors.White);
table.Cell().Text("RUT:").Bold().FontColor(Colors.White);
table.Cell().Text(row.GetValueOrDefault("Rut")?.ToString() ?? "").FontColor(Colors.White);
table.Cell().Text("Programa:").Bold().FontColor(Colors.White);
table.Cell().Text(string.Join(", ", programas.Select(p => p.GetValueOrDefault("Nombre")?.ToString()))).FontColor(Colors.White);
table.Cell().Text("Sede:").Bold().FontColor(Colors.White);
table.Cell().Text(string.Join(", ", sedes.Select(s => s.GetValueOrDefault("Nombre")?.ToString()))).FontColor(Colors.White);
table.Cell().Text("Jornada:").Bold().FontColor(Colors.White);
table.Cell().Text(string.Join(", ", jornadas.Select(j => j.GetValueOrDefault("Nombre")?.ToString()))).FontColor(Colors.White);
});
});
}
public async Task<byte[]> GenerarAnexoPdfAsync(int contratoId, int anexoId)
{
var rows = await QuerySpAsync("sige_sam_v3.PDFContratoDetalle", new { contrato = contratoId, detalle = anexoId });
var row = rows.FirstOrDefault();
if (row == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(57);
page.DefaultTextStyle(x => x.FontSize(10));
page.Header().Element(c => ComposeAnexoHeader(c, anexoId.ToString()));
page.Content().Element(c => ComposeAnexoContent(c, row));
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
});
}).GeneratePdf();
}
private void ComposeAnexoHeader(IContainer container, string numero)
{
container.Row(row =>
{
row.RelativeItem();
row.ConstantItem(120).AlignRight().Column(col =>
{
col.Item().Text($"Anexo N° {numero}").FontSize(16).Bold();
});
});
}
private void ComposeAnexoContent(IContainer container, Dictionary<string, object> row)
{
container.Column(col =>
{
col.Item().PaddingTop(20).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
foreach (var kv in row)
{
table.Cell().Text($"{kv.Key}:").Bold();
table.Cell().Text(kv.Value?.ToString() ?? "");
}
});
});
}
public async Task<byte[]> GenerarPresupuestoPdfAsync(int presupuestoId)
{
var rows = await QuerySpAsync("sige_sam_v3.BuscarCotizacionPDF", new { cotizacionid = presupuestoId });
var row = rows.FirstOrDefault();
if (row == null) return [];
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(57);
page.DefaultTextStyle(x => x.FontSize(10));
page.Header().Element(c => ComposePresupuestoHeader(c, presupuestoId.ToString()));
page.Content().Element(c => ComposePresupuestoContent(c, row));
page.Footer().AlignCenter().Text(t => { t.Span("Página "); t.CurrentPageNumber(); });
});
}).GeneratePdf();
}
private void ComposePresupuestoHeader(IContainer container, string numero)
{
container.Row(row =>
{
row.RelativeItem();
row.ConstantItem(140).AlignRight().Column(col =>
{
col.Item().Text($"Presupuesto N° {numero}").FontSize(16).Bold();
col.Item().Text("Instituto Chileno Norteamericano").FontSize(9);
});
});
}
private void ComposePresupuestoContent(IContainer container, Dictionary<string, object> row)
{
container.Column(col =>
{
col.Item().PaddingTop(20).Table(table =>
{
table.ColumnsDefinition(c => { c.ConstantColumn(120); c.RelativeColumn(); });
foreach (var kv in row)
{
table.Cell().Text($"{kv.Key}:").Bold();
table.Cell().Text(kv.Value?.ToString() ?? "");
}
});
});
}
}