Add Controller QuoteCreateFull
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 3m24s

This commit is contained in:
Leandro Hernan Rojas 2025-05-06 21:00:03 -03:00
parent b1ab76aa5a
commit 09090bef21
6 changed files with 203 additions and 2 deletions

View File

@ -41,6 +41,7 @@ namespace Models.Interfaces
#region Exportación #region Exportación
Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams); Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams);
Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
#endregion #endregion
} }

View File

@ -195,5 +195,69 @@ namespace PhronCare.Core.Services.Sales
} }
#endregion #endregion
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
{
using var transaction = await _quoteHeaderRepository.BeginTransactionAsync();
try
{
// Obtener el próximo número de presupuesto desde la serie
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
quote.Quotenumber = nextNumber.ToString();
// Crear encabezado principal
var headerEntity = await _quoteHeaderRepository.AddAsync(quote);
// Crear detalles
if (quote.PhSQuoteDetails?.Any() == true)
{
foreach (var detail in quote.PhSQuoteDetails)
{
detail.QuoteheaderId = headerEntity.Id;
await _quoteDetailRepository.AddAsync(detail);
}
}
// Crear roles
if (quote.PhSQuoteRoles?.Any() == true)
{
foreach (var role in quote.PhSQuoteRoles)
{
role.QuoteheaderId = headerEntity.Id;
await _quoteRoleRepository.AddAsync(role);
}
}
// Crear ajustes (rebajas)
if (quote.PhSQuoteAdjustments?.Any() == true)
{
foreach (var adj in quote.PhSQuoteAdjustments)
{
adj.QuoteheaderId = headerEntity.Id;
await _quoteHeaderRepository.AddAdjustmentAsync(adj);
}
}
// Crear impuestos
if (quote.PhSQuoteTaxes?.Any() == true)
{
foreach (var tax in quote.PhSQuoteTaxes)
{
tax.QuoteheaderId = headerEntity.Id;
await _quoteHeaderRepository.AddTaxAsync(tax);
}
}
await transaction.CommitAsync();
return headerEntity.Quotenumber;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
#endregion
} }
} }

View File

@ -1,5 +1,6 @@
using Domain.Entities; using Domain.Entities;
using Domain.Generics; using Domain.Generics;
using Microsoft.EntityFrameworkCore.Storage;
namespace Models.Interfaces namespace Models.Interfaces
{ {
@ -40,5 +41,7 @@ namespace Models.Interfaces
/// Elimina un impuesto asociado a un presupuesto a partir de su ID. /// Elimina un impuesto asociado a un presupuesto a partir de su ID.
/// </summary> /// </summary>
Task DeleteTaxAsync(int taxId); Task DeleteTaxAsync(int taxId);
Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
Task<IDbContextTransaction> BeginTransactionAsync();
} }
} }

View File

@ -1,15 +1,17 @@
using Domain.Entities; using Domain.Entities;
using Domain.Generics; using Domain.Generics;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Models.Helpers; using Models.Helpers;
using Models.Interfaces; using Models.Interfaces;
using Models.Models; using Models.Models;
namespace PhronCare.Core.Data.Repositories.Sales namespace PhronCare.Core.Data.Repositories.Sales
{ {
public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context) : IPhSQuoteHeaderRepository public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context, IPhSFormSeriesRepository formSeriesRepository) : IPhSQuoteHeaderRepository
{ {
private readonly PhronCareOperationsHubContext _context = context; private readonly PhronCareOperationsHubContext _context = context;
private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
public async Task<PagedResult<EQuoteHeader>> GetAllAsync(int page = 1, int pageSize = 50) public async Task<PagedResult<EQuoteHeader>> GetAllAsync(int page = 1, int pageSize = 50)
{ {
var query = _context.PhSQuoteHeaders var query = _context.PhSQuoteHeaders
@ -204,5 +206,88 @@ namespace PhronCare.Core.Data.Repositories.Sales
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
} }
} }
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
/// <summary>
/// Crea un nuevo presupuesto, incluyendo encabezado, detalles, roles, ajustes e impuestos asociados.
/// Genera automáticamente el número de presupuesto en base a la serie indicada.
/// </summary>
/// <param name="quote">Presupuesto a registrar, incluyendo entidades relacionadas.</param>
/// <param name="formSeriesId">Identificador de la serie de numeración a utilizar.</param>
/// <returns>Cadena con el número generado del presupuesto.</returns>
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
// Obtener el próximo número de presupuesto desde SP
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
quote.Quotenumber = nextNumber.ToString();
// Map y guardado de Header
var headerEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quote);
_context.PhSQuoteHeaders.Add(headerEntity);
await _context.SaveChangesAsync();
// Guardado de Detalles
if (quote.PhSQuoteDetails?.Any() == true)
{
foreach (var detail in quote.PhSQuoteDetails)
{
detail.QuoteheaderId = headerEntity.Id;
var dbDetail = EntityMapper.MapEntity<EQuoteDetail, PhSQuoteDetail>(detail);
_context.PhSQuoteDetails.Add(dbDetail);
}
}
// Guardado de Roles
if (quote.PhSQuoteRoles?.Any() == true)
{
foreach (var role in quote.PhSQuoteRoles)
{
role.QuoteheaderId = headerEntity.Id;
var dbRole = EntityMapper.MapEntity<EQuoteRole, PhSQuoteRole>(role);
_context.PhSQuoteRoles.Add(dbRole);
}
}
// Guardado de Ajustes
if (quote.PhSQuoteAdjustments?.Any() == true)
{
foreach (var adj in quote.PhSQuoteAdjustments)
{
adj.QuoteheaderId = headerEntity.Id;
var dbAdj = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adj);
_context.PhSQuoteAdjustments.Add(dbAdj);
}
}
// Guardado de Impuestos
if (quote.PhSQuoteTaxes?.Any() == true)
{
foreach (var tax in quote.PhSQuoteTaxes)
{
tax.QuoteheaderId = headerEntity.Id;
var dbTax = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
_context.PhSQuoteTaxes.Add(dbTax);
}
}
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return headerEntity.Quotenumber;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
public async Task<IDbContextTransaction> BeginTransactionAsync()
{
return await _context.Database.BeginTransactionAsync();
}
#endregion
} }
} }

View File

@ -312,5 +312,32 @@ namespace phronCare.API.Controllers.Sales
#endregion #endregion
#region Endpoint de emision de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
[HttpPost("createfull")]
public async Task<IActionResult> CreateFullQuote([FromBody] EQuoteHeader quote, [FromQuery] int formSeriesId)
{
try
{
if (quote == null)
return BadRequest("El presupuesto no puede ser nulo.");
var quoteNumber = await _quoteService.CreateFullQuoteAsync(quote, formSeriesId);
return Ok(new { QuoteNumber = quoteNumber });
}
catch (ArgumentNullException ex)
{
return BadRequest($"Validación fallida: {ex.Message}");
}
catch (InvalidOperationException ex)
{
return BadRequest($"Error de negocio: {ex.Message}");
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
return StatusCode(500, $"{methodName} Message: {ex.Message}");
}
}
#endregion
} }
} }

View File

@ -1704,6 +1704,27 @@
], ],
"ReturnTypes": [] "ReturnTypes": []
}, },
{
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
"Method": "CreateFullQuote",
"RelativePath": "api/Quote/createfull",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "quote",
"Type": "Domain.Entities.EQuoteHeader",
"IsRequired": true
},
{
"Name": "formSeriesId",
"Type": "System.Int32",
"IsRequired": false
}
],
"ReturnTypes": []
},
{ {
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
"Method": "Delete", "Method": "Delete",