From 09090bef21ca56a8641507894865902f06553451 Mon Sep 17 00:00:00 2001 From: Leandro Hernan Rojas Date: Tue, 6 May 2025 21:00:03 -0300 Subject: [PATCH] Add Controller QuoteCreateFull --- Core/Interfaces/IQuoteDom.cs | 1 + Core/Services/QuoteService.cs | 66 +++++++++++++- .../Interfaces/IPhSQuoteHeaderRepository.cs | 3 + .../Repositories/PhSQuoteHeaderRepository.cs | 87 ++++++++++++++++++- .../Controllers/Sales/QuoteController.cs | 27 ++++++ .../obj/Debug/net8.0/ApiEndpoints.json | 21 +++++ 6 files changed, 203 insertions(+), 2 deletions(-) diff --git a/Core/Interfaces/IQuoteDom.cs b/Core/Interfaces/IQuoteDom.cs index 82bdd76..cb41289 100644 --- a/Core/Interfaces/IQuoteDom.cs +++ b/Core/Interfaces/IQuoteDom.cs @@ -41,6 +41,7 @@ namespace Models.Interfaces #region Exportación Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams); + Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId); #endregion } diff --git a/Core/Services/QuoteService.cs b/Core/Services/QuoteService.cs index e68eda7..cbdec64 100644 --- a/Core/Services/QuoteService.cs +++ b/Core/Services/QuoteService.cs @@ -195,5 +195,69 @@ namespace PhronCare.Core.Services.Sales } #endregion + #region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos) + public async Task 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 } -} +} \ No newline at end of file diff --git a/Models/Interfaces/IPhSQuoteHeaderRepository.cs b/Models/Interfaces/IPhSQuoteHeaderRepository.cs index 5821577..cade50d 100644 --- a/Models/Interfaces/IPhSQuoteHeaderRepository.cs +++ b/Models/Interfaces/IPhSQuoteHeaderRepository.cs @@ -1,5 +1,6 @@ using Domain.Entities; using Domain.Generics; +using Microsoft.EntityFrameworkCore.Storage; namespace Models.Interfaces { @@ -40,5 +41,7 @@ namespace Models.Interfaces /// Elimina un impuesto asociado a un presupuesto a partir de su ID. /// Task DeleteTaxAsync(int taxId); + Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId); + Task BeginTransactionAsync(); } } diff --git a/Models/Repositories/PhSQuoteHeaderRepository.cs b/Models/Repositories/PhSQuoteHeaderRepository.cs index c7f8677..02c20d9 100644 --- a/Models/Repositories/PhSQuoteHeaderRepository.cs +++ b/Models/Repositories/PhSQuoteHeaderRepository.cs @@ -1,15 +1,17 @@ using Domain.Entities; using Domain.Generics; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Models.Helpers; using Models.Interfaces; using Models.Models; 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 IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository; public async Task> GetAllAsync(int page = 1, int pageSize = 50) { var query = _context.PhSQuoteHeaders @@ -204,5 +206,88 @@ namespace PhronCare.Core.Data.Repositories.Sales await _context.SaveChangesAsync(); } } + + #region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos) + /// + /// 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. + /// + /// Presupuesto a registrar, incluyendo entidades relacionadas. + /// Identificador de la serie de numeración a utilizar. + /// Cadena con el número generado del presupuesto. + public async Task 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(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(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(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(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(tax); + _context.PhSQuoteTaxes.Add(dbTax); + } + } + + await _context.SaveChangesAsync(); + await transaction.CommitAsync(); + + return headerEntity.Quotenumber; + } + catch + { + await transaction.RollbackAsync(); + throw; + } + } + public async Task BeginTransactionAsync() + { + return await _context.Database.BeginTransactionAsync(); + } + #endregion } } \ No newline at end of file diff --git a/phronCare.API/Controllers/Sales/QuoteController.cs b/phronCare.API/Controllers/Sales/QuoteController.cs index 230ec23..cf3a016 100644 --- a/phronCare.API/Controllers/Sales/QuoteController.cs +++ b/phronCare.API/Controllers/Sales/QuoteController.cs @@ -312,5 +312,32 @@ namespace phronCare.API.Controllers.Sales #endregion + #region Endpoint de emision de presupuesto (encabezado + detalles + roles + ajustes + impuestos) + [HttpPost("createfull")] + public async Task 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 } } \ No newline at end of file diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json index f146b03..a0f0361 100644 --- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json +++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json @@ -1704,6 +1704,27 @@ ], "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", "Method": "Delete",