diff --git a/Core/Interfaces/IQuoteDom.cs b/Core/Interfaces/IQuoteDom.cs index cb41289..677edea 100644 --- a/Core/Interfaces/IQuoteDom.cs +++ b/Core/Interfaces/IQuoteDom.cs @@ -20,29 +20,14 @@ namespace Models.Interfaces string? status, int page = 1, int pageSize = 50); - Task CreateQuoteAsync(EQuoteHeader quote, int formSeriesId); Task UpdateQuoteAsync(EQuoteHeader quote); Task DeleteQuoteAsync(int id); #endregion - - #region Ajustes - Task> GetAdjustmentsByQuoteIdAsync(int quoteId); - Task AddAdjustmentAsync(EQuoteAdjustment adjustment); - Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment); - Task DeleteAdjustmentAsync(int adjustmentId); - #endregion - - #region Impuestos - Task> GetTaxesByQuoteIdAsync(int quoteId); - Task AddTaxAsync(EQuoteTax tax); - Task UpdateTaxAsync(EQuoteTax tax); - Task DeleteTaxAsync(int taxId); - #endregion - #region Exportación Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams); + #endregion + #region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos) Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId); #endregion - } } \ No newline at end of file diff --git a/Core/Services/QuoteService.cs b/Core/Services/QuoteService.cs index cbdec64..b458cb3 100644 --- a/Core/Services/QuoteService.cs +++ b/Core/Services/QuoteService.cs @@ -9,16 +9,12 @@ namespace PhronCare.Core.Services.Sales { public class QuoteService( IPhSQuoteHeaderRepository quoteHeaderRepository, - IPhSQuoteDetailRepository quoteDetailRepository, - IPhSQuoteRoleRepository quoteRoleRepository, - IPhSFormSeriesRepository formSeriesRepository - ) : IQuoteDom + IPhSQuoteRepository quoteRepository + ) : IQuoteDom { #region Declaraciones private readonly IPhSQuoteHeaderRepository _quoteHeaderRepository = quoteHeaderRepository; - private readonly IPhSQuoteDetailRepository _quoteDetailRepository = quoteDetailRepository; - private readonly IPhSQuoteRoleRepository _quoteRoleRepository = quoteRoleRepository; - private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository; + private readonly IPhSQuoteRepository _quoteRepository = quoteRepository; #endregion #region Presupuestos @@ -26,17 +22,14 @@ namespace PhronCare.Core.Services.Sales { return await _quoteHeaderRepository.GetAllAsync(page, pageSize); } - public async Task GetQuoteByIdAsync(int id) { return await _quoteHeaderRepository.GetByIdAsync(id); } - public async Task> GetQuotesByCustomerAsync(int customerId) { return await _quoteHeaderRepository.GetByCustomerIdAsync(customerId); } - public async Task> SearchQuotesAsync( int? customerId, string? quoteNumber, @@ -61,94 +54,16 @@ namespace PhronCare.Core.Services.Sales page, pageSize); } - - public async Task CreateQuoteAsync(EQuoteHeader quote, int formSeriesId) - { - // Obtener el próximo número de documento - var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId); - quote.Quotenumber = nextNumber.ToString(); - - // Crear encabezado - var newQuote = await _quoteHeaderRepository.AddAsync(quote); - - // Crear detalles asociados - if (quote.PhSQuoteDetails != null) - { - foreach (var detail in quote.PhSQuoteDetails) - { - detail.QuoteheaderId = newQuote.Id; - await _quoteDetailRepository.AddAsync(detail); - } - } - - // Crear roles asociados - if (quote.PhSQuoteRoles != null) - { - foreach (var role in quote.PhSQuoteRoles) - { - role.QuoteheaderId = newQuote.Id; - await _quoteRoleRepository.AddAsync(role); - } - } - - return newQuote; - } - public async Task UpdateQuoteAsync(EQuoteHeader quote) { await _quoteHeaderRepository.UpdateAsync(quote); } - public async Task DeleteQuoteAsync(int id) { await _quoteHeaderRepository.DeleteAsync(id); } #endregion - #region Ajustes - public async Task> GetAdjustmentsByQuoteIdAsync(int quoteId) - { - return await _quoteHeaderRepository.GetAdjustmentsByQuoteIdAsync(quoteId); - } - - public async Task AddAdjustmentAsync(EQuoteAdjustment adjustment) - { - return await _quoteHeaderRepository.AddAdjustmentAsync(adjustment); - } - - public async Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment) - { - await _quoteHeaderRepository.UpdateAdjustmentAsync(adjustment); - } - - public async Task DeleteAdjustmentAsync(int adjustmentId) - { - await _quoteHeaderRepository.DeleteAdjustmentAsync(adjustmentId); - } - #endregion - - #region Impuestos - public async Task> GetTaxesByQuoteIdAsync(int quoteId) - { - return await _quoteHeaderRepository.GetTaxesByQuoteIdAsync(quoteId); - } - - public async Task AddTaxAsync(EQuoteTax tax) - { - return await _quoteHeaderRepository.AddTaxAsync(tax); - } - - public async Task UpdateTaxAsync(EQuoteTax tax) - { - await _quoteHeaderRepository.UpdateTaxAsync(tax); - } - - public async Task DeleteTaxAsync(int taxId) - { - await _quoteHeaderRepository.DeleteTaxAsync(taxId); - } - #endregion - #region Exportación public async Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams) { @@ -198,66 +113,65 @@ namespace PhronCare.Core.Services.Sales #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; - } + // 1. Validaciones antes de iniciar transacción + ValidateQuote(quote); + return await _quoteRepository.CreateFullQuoteAsync(quote, formSeriesId); } #endregion + + #region Validaciones QuoteCreate + private void ValidateQuote(EQuoteHeader quote) + { + if (quote == null) + throw new ArgumentNullException(nameof(quote), "El presupuesto no puede ser nulo."); + + if (quote.CustomerId <= 0) + throw new ArgumentException("Debe seleccionar un cliente."); + + if (quote.PeopleId <= 0) + throw new ArgumentException("Debe seleccionar un vendedor."); + + if (string.IsNullOrWhiteSpace(quote.Currency)) + throw new ArgumentException("La moneda es obligatoria."); + + if (quote.PhSQuoteDetails == null || !quote.PhSQuoteDetails.Any()) + throw new InvalidOperationException("Debe incluir al menos un producto."); + + foreach (var detail in quote.PhSQuoteDetails) + { + if (detail.Quantity <= 0) + throw new ArgumentException($"La cantidad para el producto {detail.ProductId} debe ser mayor a cero."); + if (detail.Unitprice < 0) + throw new ArgumentException($"El precio unitario del producto {detail.ProductId} no puede ser negativo."); + } + + if (quote.PhSQuoteRoles == null || !quote.PhSQuoteRoles.Any()) + throw new InvalidOperationException("Debe asignar al menos un rol (profesional, paciente o institución)."); + + var hasProfessional = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Professional); + var hasPatient = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Patient); + var hasInstitution = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Institution); + + if (!hasProfessional) + throw new InvalidOperationException("Debe asignar un profesional."); + if (!hasInstitution) + throw new InvalidOperationException("Debe asignar un paciente."); + if (!hasPatient) + throw new InvalidOperationException("Debe asignar un paciente."); + + if (quote.PhSQuoteTaxes != null) + { + foreach (var tax in quote.PhSQuoteTaxes) + { + if (tax.Taxrate < 0 || tax.Taxrate > 100) + throw new ArgumentException($"La alícuota del impuesto '{tax.Taxname}' no es válida."); + } + } + + if (quote.Total < 0) + throw new ArgumentException("El total del presupuesto no puede ser negativo."); + } + + #endregion } } \ No newline at end of file diff --git a/Domain/Entities/EQuoteAdjustment.cs b/Domain/Entities/EQuoteAdjustment.cs index 42ba630..643115d 100644 --- a/Domain/Entities/EQuoteAdjustment.cs +++ b/Domain/Entities/EQuoteAdjustment.cs @@ -18,7 +18,7 @@ /// /// Código del motivo de ajuste (FK a PhS_AdjustmentReasons) /// - public string ReasonCode { get; set; } = null!; + public string ReasonCode { get; set; } = String.Empty; /// /// Importe del ajuste realizado (positivo o nulo) @@ -28,7 +28,7 @@ /// /// Descripción adicional del ajuste /// - public string? Description { get; set; } + public string? Description { get; set; } = String.Empty; /// /// Fecha de registro del ajuste diff --git a/Domain/Entities/EQuoteDetail.cs b/Domain/Entities/EQuoteDetail.cs index 9153e10..6276dc3 100644 --- a/Domain/Entities/EQuoteDetail.cs +++ b/Domain/Entities/EQuoteDetail.cs @@ -20,7 +20,7 @@ /// /// Descripción modificable del producto (puede diferir del original) /// - public string? ProductDescription { get; set; } + public string? ProductDescription { get; set; }=String.Empty; /// /// Cantidad @@ -52,8 +52,8 @@ /// public DateTime? Modifiedat { get; set; } - public virtual EProduct Product { get; set; } = null!; + //public virtual EProduct Product { get; set; } = null!; - public virtual EQuoteHeader PhSQuoteheader { get; set; } = null!; + //public virtual EQuoteHeader PhSQuoteheader { get; set; } = null!; } } diff --git a/Domain/Entities/EQuoteHeader.cs b/Domain/Entities/EQuoteHeader.cs index 54870de..9483c16 100644 --- a/Domain/Entities/EQuoteHeader.cs +++ b/Domain/Entities/EQuoteHeader.cs @@ -1,4 +1,6 @@ -namespace Domain.Entities +using System.Text.Json.Serialization; + +namespace Domain.Entities { /// /// Tabla de cabeceras de presupuestos @@ -19,7 +21,8 @@ /// /// Número visible del presupuesto /// - public string Quotenumber { get; set; } = null!; + + public string Quotenumber { get; set; } = String.Empty; /// /// Cliente asociado @@ -54,17 +57,17 @@ /// /// Código de moneda pactada (ISO 4217). Ej: ARS, USD /// - public string Currency { get; set; } = null!; + public string Currency { get; set; }= String.Empty; /// /// Tipo de cambio pactado para conversión a pesos argentinos /// - public decimal? Exchangerate { get; set; } + public decimal Exchangerate { get; set; } /// /// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto /// - public decimal? Netamount { get; set; } + public decimal Netamount { get; set; } /// /// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales @@ -84,7 +87,7 @@ /// /// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc. /// - public string Status { get; set; } = null!; + public string Status { get; set; } = String.Empty; /// /// Indica si la cirugía se realizará fuera de la ciudad/localidad habitual (“out of town”) @@ -94,7 +97,7 @@ /// /// Instrucción dirigida al área de logística para detallar qué debe prepararse o despacharse (ej: “CMF 1.5 + INSTRUMENTAL”) /// - public string? DispatchInstruction { get; set; } + public string? DispatchInstruction { get; set; } = String.Empty; /// /// Cantidad de impresiones @@ -104,7 +107,7 @@ /// /// Observaciones internas /// - public string? Observations { get; set; } + public string? Observations { get; set; } = String.Empty; /// /// Fecha de creación @@ -115,13 +118,9 @@ /// Fecha de modificación /// public DateTime? Modifiedat { get; set; } - public virtual ICollection PhSQuoteAdjustments { get; set; } = new List(); - public virtual ICollection PhSQuoteDetails { get; set; } = new List(); - public virtual ICollection PhSQuoteRoles { get; set; } = new List(); - public virtual ICollection PhSQuoteTaxes { get; set; } = new List(); } } diff --git a/Domain/Entities/EQuoteRole.cs b/Domain/Entities/EQuoteRole.cs index 211f042..33761b3 100644 --- a/Domain/Entities/EQuoteRole.cs +++ b/Domain/Entities/EQuoteRole.cs @@ -21,7 +21,7 @@ namespace Domain.Entities /// /// Tipo de entidad asociada (Ej: PhS_Professionals, PhS_Institutions, PhS_Patients) /// - public string Entitytype { get; set; } = null!; + public string Entitytype { get; set; } = String.Empty; /// /// ID de la entidad asociada diff --git a/Domain/Entities/EQuoteTax.cs b/Domain/Entities/EQuoteTax.cs index fd13a3e..16fd2c5 100644 --- a/Domain/Entities/EQuoteTax.cs +++ b/Domain/Entities/EQuoteTax.cs @@ -15,12 +15,12 @@ /// /// Nombre descriptivo del impuesto /// - public string Taxname { get; set; } = null!; + public string Taxname { get; set; } = String.Empty; /// /// Código o identificador oficial del impuesto (ej. AFIP) /// - public string? Taxcode { get; set; } + public string? Taxcode { get; set; } = String.Empty; /// /// Base imponible del impuesto (importe gravado) diff --git a/Models/Interfaces/IPhSFormSeriesRepository.cs b/Models/Interfaces/IPhSFormSeriesRepository.cs index ba8584e..e0f4278 100644 --- a/Models/Interfaces/IPhSFormSeriesRepository.cs +++ b/Models/Interfaces/IPhSFormSeriesRepository.cs @@ -1,7 +1,10 @@ -namespace Models.Interfaces +using Models.Models; + +namespace Models.Interfaces { public interface IPhSFormSeriesRepository { + Task GetByIdAsync(int formSeriesId); Task GetNextInternalNumberAsync(int formSeriesId); } } diff --git a/Models/Interfaces/IPhSQuoteHeaderRepository.cs b/Models/Interfaces/IPhSQuoteHeaderRepository.cs index cade50d..0d318c4 100644 --- a/Models/Interfaces/IPhSQuoteHeaderRepository.cs +++ b/Models/Interfaces/IPhSQuoteHeaderRepository.cs @@ -1,6 +1,5 @@ using Domain.Entities; using Domain.Generics; -using Microsoft.EntityFrameworkCore.Storage; namespace Models.Interfaces { @@ -12,36 +11,34 @@ namespace Models.Interfaces Task> SearchAsync(int? customerId, string? quoteNumber, int? professionalId, int? institutionId, int? patientId, DateTime? issueDateFrom, DateTime? issueDateTo, string? status, int page = 1, int pageSize = 50); - - Task AddAsync(EQuoteHeader quoteHeader); Task UpdateAsync(EQuoteHeader quoteHeader); Task DeleteAsync(int id); // Ajustes - Task> GetAdjustmentsByQuoteIdAsync(int quoteId); - Task AddAdjustmentAsync(EQuoteAdjustment adjustment); - Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment); - Task DeleteAdjustmentAsync(int adjustmentId); - /// - /// Obtiene todos los impuestos asociados a un presupuesto dado por su ID. - /// - Task> GetTaxesByQuoteIdAsync(int quoteId); + //Task> GetAdjustmentsByQuoteIdAsync(int quoteId); + //Task AddAdjustmentAsync(EQuoteAdjustment adjustment); + //Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment); + //Task DeleteAdjustmentAsync(int adjustmentId); + ///// + ///// Obtiene todos los impuestos asociados a un presupuesto dado por su ID. + ///// + //Task> GetTaxesByQuoteIdAsync(int quoteId); - /// - /// Agrega un nuevo impuesto al presupuesto correspondiente. - /// - Task AddTaxAsync(EQuoteTax tax); + ///// + ///// Agrega un nuevo impuesto al presupuesto correspondiente. + ///// + //Task AddTaxAsync(EQuoteTax tax); - /// - /// Actualiza los datos de un impuesto existente en un presupuesto. - /// - Task UpdateTaxAsync(EQuoteTax tax); + ///// + ///// Actualiza los datos de un impuesto existente en un presupuesto. + ///// + //Task UpdateTaxAsync(EQuoteTax tax); - /// - /// Elimina un impuesto asociado a un presupuesto a partir de su ID. - /// - Task DeleteTaxAsync(int taxId); - Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId); - Task BeginTransactionAsync(); + ///// + ///// 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/Interfaces/IPhSQuoteRepository.cs b/Models/Interfaces/IPhSQuoteRepository.cs new file mode 100644 index 0000000..9109617 --- /dev/null +++ b/Models/Interfaces/IPhSQuoteRepository.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Models.Interfaces +{ + public interface IPhSQuoteRepository + { + Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId); + } +} diff --git a/Models/Repositories/PhSFormSeriesRepository.cs b/Models/Repositories/PhSFormSeriesRepository.cs index 035165d..34fae70 100644 --- a/Models/Repositories/PhSFormSeriesRepository.cs +++ b/Models/Repositories/PhSFormSeriesRepository.cs @@ -29,7 +29,12 @@ namespace PhronCare.Core.Data.Repositories.Sales return (int)nextNumberParam.Value; } - + public async Task GetByIdAsync(int formSeriesId) + { + return await _context.PhSFormSeries + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == formSeriesId); + } #endregion } } diff --git a/Models/Repositories/PhSQuoteHeaderRepository.cs b/Models/Repositories/PhSQuoteHeaderRepository.cs index 02c20d9..01cec70 100644 --- a/Models/Repositories/PhSQuoteHeaderRepository.cs +++ b/Models/Repositories/PhSQuoteHeaderRepository.cs @@ -1,17 +1,17 @@ -using Domain.Entities; -using Domain.Generics; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using Models.Helpers; +using Microsoft.EntityFrameworkCore; using Models.Interfaces; +using Models.Helpers; using Models.Models; +using Domain.Entities; +using Domain.Generics; namespace PhronCare.Core.Data.Repositories.Sales { - public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context, IPhSFormSeriesRepository formSeriesRepository) : IPhSQuoteHeaderRepository + public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context) : IPhSQuoteHeaderRepository { private readonly PhronCareOperationsHubContext _context = context; - private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository; + //private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository; + #region Metodos public async Task> GetAllAsync(int page = 1, int pageSize = 50) { var query = _context.PhSQuoteHeaders @@ -100,13 +100,7 @@ namespace PhronCare.Core.Data.Repositories.Sales PageSize = pagedEntities.PageSize }; } - public async Task AddAsync(EQuoteHeader quoteHeader) - { - var dbEntity = EntityMapper.MapEntity(quoteHeader); - _context.PhSQuoteHeaders.Add(dbEntity); - await _context.SaveChangesAsync(); - return EntityMapper.MapEntity(dbEntity); - } + public async Task UpdateAsync(EQuoteHeader quoteHeader) { var dbEntity = EntityMapper.MapEntity(quoteHeader); @@ -122,172 +116,173 @@ namespace PhronCare.Core.Data.Repositories.Sales await _context.SaveChangesAsync(); } } - + #endregion + #region // ---------------------------- // Métodos para Ajustes // ---------------------------- - public async Task> GetAdjustmentsByQuoteIdAsync(int quoteId) - { - var adjustments = await _context.PhSQuoteAdjustments - .Where(a => a.QuoteheaderId == quoteId) - .ToListAsync(); + //public async Task> GetAdjustmentsByQuoteIdAsync(int quoteId) + //{ + // var adjustments = await _context.PhSQuoteAdjustments + // .Where(a => a.QuoteheaderId == quoteId) + // .ToListAsync(); - return adjustments.Select(EntityMapper.MapEntity); - } - public async Task AddAdjustmentAsync(EQuoteAdjustment adjustment) - { - var dbEntity = EntityMapper.MapEntity(adjustment); - _context.PhSQuoteAdjustments.Add(dbEntity); - await _context.SaveChangesAsync(); - return EntityMapper.MapEntity(dbEntity); - } - public async Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment) - { - var dbEntity = EntityMapper.MapEntity(adjustment); - _context.PhSQuoteAdjustments.Update(dbEntity); - await _context.SaveChangesAsync(); - } - public async Task DeleteAdjustmentAsync(int adjustmentId) - { - var entity = await _context.PhSQuoteAdjustments.FindAsync(adjustmentId); - if (entity != null) - { - _context.PhSQuoteAdjustments.Remove(entity); - await _context.SaveChangesAsync(); - } - } + // return adjustments.Select(EntityMapper.MapEntity); + //} + //public async Task AddAdjustmentAsync(EQuoteAdjustment adjustment) + //{ + // var dbEntity = EntityMapper.MapEntity(adjustment); + // _context.PhSQuoteAdjustments.Add(dbEntity); + // await _context.SaveChangesAsync(); + // return EntityMapper.MapEntity(dbEntity); + //} + //public async Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment) + //{ + // var dbEntity = EntityMapper.MapEntity(adjustment); + // _context.PhSQuoteAdjustments.Update(dbEntity); + // await _context.SaveChangesAsync(); + //} + //public async Task DeleteAdjustmentAsync(int adjustmentId) + //{ + // var entity = await _context.PhSQuoteAdjustments.FindAsync(adjustmentId); + // if (entity != null) + // { + // _context.PhSQuoteAdjustments.Remove(entity); + // await _context.SaveChangesAsync(); + // } + //} - // ---------------------------- - // Métodos para Impuestos - // ---------------------------- + //// ---------------------------- + //// Métodos para Impuestos + //// ---------------------------- - /// - /// Obtiene todos los impuestos asociados a un presupuesto dado por su ID. - /// - public async Task> GetTaxesByQuoteIdAsync(int quoteId) - { - var taxes = await _context.PhSQuoteTaxes - .Where(t => t.QuoteheaderId == quoteId) - .ToListAsync(); + ///// + ///// Obtiene todos los impuestos asociados a un presupuesto dado por su ID. + ///// + //public async Task> GetTaxesByQuoteIdAsync(int quoteId) + //{ + // var taxes = await _context.PhSQuoteTaxes + // .Where(t => t.QuoteheaderId == quoteId) + // .ToListAsync(); - return taxes.Select(EntityMapper.MapEntity); - } + // return taxes.Select(EntityMapper.MapEntity); + //} - /// - /// Agrega un nuevo impuesto al presupuesto correspondiente. - /// - public async Task AddTaxAsync(EQuoteTax tax) - { - var dbEntity = EntityMapper.MapEntity(tax); - _context.PhSQuoteTaxes.Add(dbEntity); - await _context.SaveChangesAsync(); - return EntityMapper.MapEntity(dbEntity); - } + ///// + ///// Agrega un nuevo impuesto al presupuesto correspondiente. + ///// + //public async Task AddTaxAsync(EQuoteTax tax) + //{ + // var dbEntity = EntityMapper.MapEntity(tax); + // _context.PhSQuoteTaxes.Add(dbEntity); + // await _context.SaveChangesAsync(); + // return EntityMapper.MapEntity(dbEntity); + //} - /// - /// Actualiza los datos de un impuesto existente en un presupuesto. - /// - public async Task UpdateTaxAsync(EQuoteTax tax) - { - var dbEntity = EntityMapper.MapEntity(tax); - _context.PhSQuoteTaxes.Update(dbEntity); - await _context.SaveChangesAsync(); - } + ///// + ///// Actualiza los datos de un impuesto existente en un presupuesto. + ///// + //public async Task UpdateTaxAsync(EQuoteTax tax) + //{ + // var dbEntity = EntityMapper.MapEntity(tax); + // _context.PhSQuoteTaxes.Update(dbEntity); + // await _context.SaveChangesAsync(); + //} - /// - /// Elimina un impuesto asociado a un presupuesto a partir de su ID. - /// - public async Task DeleteTaxAsync(int taxId) - { - var entity = await _context.PhSQuoteTaxes.FindAsync(taxId); - if (entity != null) - { - _context.PhSQuoteTaxes.Remove(entity); - await _context.SaveChangesAsync(); - } - } + ///// + ///// Elimina un impuesto asociado a un presupuesto a partir de su ID. + ///// + //public async Task DeleteTaxAsync(int taxId) + //{ + // var entity = await _context.PhSQuoteTaxes.FindAsync(taxId); + // if (entity != null) + // { + // _context.PhSQuoteTaxes.Remove(entity); + // 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(); + //#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(); + // // 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 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 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 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); - } - } + // // 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(); + // await _context.SaveChangesAsync(); + // await transaction.CommitAsync(); - return headerEntity.Quotenumber; - } - catch - { - await transaction.RollbackAsync(); - throw; - } - } - public async Task BeginTransactionAsync() - { - return await _context.Database.BeginTransactionAsync(); - } + // 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/Models/Repositories/PhSQuoteRepository.cs b/Models/Repositories/PhSQuoteRepository.cs new file mode 100644 index 0000000..4e462a8 --- /dev/null +++ b/Models/Repositories/PhSQuoteRepository.cs @@ -0,0 +1,95 @@ +using Domain.Entities; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace Models.Repositories +{ + public class PhSQuoteRepository(PhronCareOperationsHubContext context, + IPhSFormSeriesRepository formSeriesRepository) : IPhSQuoteRepository + { + private readonly PhronCareOperationsHubContext _context = context; + private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository; + + #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 + { + var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId); + var series = await _formSeriesRepository.GetByIdAsync(formSeriesId) + ?? throw new InvalidOperationException("Serie no encontrada"); + int padding = 8; /* format "00000000" */ + + quote.Quotenumber = $"{series.Letter}-{nextNumber.ToString($"D{padding}")}"; + + var headerEntity = EntityMapper.MapEntity(quote); + _context.PhSQuoteHeaders.Add(headerEntity); + + #region Nota: Esta seccion queda para futura modificacion en caso de cambiar CASCADE INSERT de las entidades relacionadas + //// 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); + // } + //} + #endregion + + await _context.SaveChangesAsync(); + await transaction.CommitAsync(); + return headerEntity.Quotenumber; + } + catch + { + await transaction.RollbackAsync(); + throw; + } + } + #endregion + } +} \ No newline at end of file diff --git a/phronCare.API/Controllers/Sales/QuoteController.cs b/phronCare.API/Controllers/Sales/QuoteController.cs index cf3a016..61e038e 100644 --- a/phronCare.API/Controllers/Sales/QuoteController.cs +++ b/phronCare.API/Controllers/Sales/QuoteController.cs @@ -91,32 +91,6 @@ namespace phronCare.API.Controllers.Sales #region Crear / Actualizar / Eliminar - [HttpPost("create")] - public async Task Create([FromBody] EQuoteHeader quote, [FromQuery] int formSeriesId) - { - try - { - if (quote == null) - return BadRequest("El presupuesto no puede ser nulo."); - - var result = await _quoteService.CreateQuoteAsync(quote, formSeriesId); - return Ok(result); - } - 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}"); - } - } - [HttpPut("update")] public async Task Update([FromBody] EQuoteHeader quote) { @@ -152,145 +126,6 @@ namespace phronCare.API.Controllers.Sales #endregion - #region Impuestos (QuoteTaxes) - - [HttpGet("{quoteId:int}/taxes")] - public async Task GetTaxes(int quoteId) - { - try - { - var taxes = await _quoteService.GetTaxesByQuoteIdAsync(quoteId); - return Ok(taxes); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - [HttpPost("{quoteId:int}/taxes")] - public async Task AddTax(int quoteId, [FromBody] EQuoteTax tax) - { - try - { - if (tax == null || quoteId != tax.QuoteheaderId) - return BadRequest("Datos inválidos para el impuesto."); - - var result = await _quoteService.AddTaxAsync(tax); - return Ok(result); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - [HttpPut("taxes")] - public async Task UpdateTax([FromBody] EQuoteTax tax) - { - try - { - if (tax == null || tax.Id <= 0) - return BadRequest("Datos inválidos para actualizar el impuesto."); - - await _quoteService.UpdateTaxAsync(tax); - return Ok("Impuesto actualizado correctamente."); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - [HttpDelete("taxes/{taxId:int}")] - public async Task DeleteTax(int taxId) - { - try - { - await _quoteService.DeleteTaxAsync(taxId); - return Ok("Impuesto eliminado correctamente."); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - #endregion - - #region Ajustes Comerciales (QuoteAdjustments) - - [HttpGet("{quoteId:int}/adjustments")] - public async Task GetAdjustments(int quoteId) - { - try - { - var adjustments = await _quoteService.GetAdjustmentsByQuoteIdAsync(quoteId); - return Ok(adjustments); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - [HttpPost("{quoteId:int}/adjustments")] - public async Task AddAdjustment(int quoteId, [FromBody] EQuoteAdjustment adjustment) - { - try - { - if (adjustment == null || quoteId != adjustment.QuoteheaderId) - return BadRequest("Datos inválidos para el ajuste."); - - var result = await _quoteService.AddAdjustmentAsync(adjustment); - return Ok(result); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - [HttpPut("adjustments")] - public async Task UpdateAdjustment([FromBody] EQuoteAdjustment adjustment) - { - try - { - if (adjustment == null || adjustment.Id <= 0) - return BadRequest("Datos inválidos para actualizar el ajuste."); - - await _quoteService.UpdateAdjustmentAsync(adjustment); - return Ok("Ajuste actualizado correctamente."); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - [HttpDelete("adjustments/{adjustmentId:int}")] - public async Task DeleteAdjustment(int adjustmentId) - { - try - { - await _quoteService.DeleteAdjustmentAsync(adjustmentId); - return Ok("Ajuste eliminado correctamente."); - } - catch (Exception ex) - { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); - } - } - - #endregion #region Exportación @@ -314,15 +149,23 @@ namespace phronCare.API.Controllers.Sales #region Endpoint de emision de presupuesto (encabezado + detalles + roles + ajustes + impuestos) [HttpPost("createfull")] - public async Task CreateFullQuote([FromBody] EQuoteHeader quote, [FromQuery] int formSeriesId) + public async Task CreateFullQuote([FromBody] CreateFullQuoteRequest request) { try { - if (quote == null) - return BadRequest("El presupuesto no puede ser nulo."); + // Validamos que el request y el objeto Quote no sean nulos + if (request == null || request.Quote == null) + return BadRequest("El payload no puede contener elementos nulos."); + // Desempaquetamos los datos + var quote = request.Quote; + var formSeriesId = request.FormSeriesId; + + // Llamada al servicio de negocio var quoteNumber = await _quoteService.CreateFullQuoteAsync(quote, formSeriesId); - return Ok(new { QuoteNumber = quoteNumber }); + + // Devolvemos el número generado + return Ok(new { Success = true, QuoteNumber = quoteNumber }); } catch (ArgumentNullException ex) { @@ -334,10 +177,16 @@ namespace phronCare.API.Controllers.Sales } catch (Exception ex) { - var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; - return StatusCode(500, $"{methodName} Message: {ex.Message}"); + return StatusCode(500, $"Ocurrió un error interno: {ex.Message}"); } } + + public class CreateFullQuoteRequest + { + public EQuoteHeader Quote { get; set; } = default!; + public int FormSeriesId { get; set; } + } + #endregion } } \ No newline at end of file diff --git a/phronCare.API/Program.cs b/phronCare.API/Program.cs index d3c6ec2..0cb35ab 100644 --- a/phronCare.API/Program.cs +++ b/phronCare.API/Program.cs @@ -244,8 +244,8 @@ static void RepositorysAndServices(WebApplicationBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); + //builder.Services.AddScoped(); builder.Services.AddScoped(); // Registrar el service de lookup diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json index a0f0361..b43fff1 100644 --- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json +++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json @@ -1556,112 +1556,6 @@ } ] }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "GetAdjustments", - "RelativePath": "api/Quote/{quoteId}/adjustments", - "HttpMethod": "GET", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "quoteId", - "Type": "System.Int32", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "AddAdjustment", - "RelativePath": "api/Quote/{quoteId}/adjustments", - "HttpMethod": "POST", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "quoteId", - "Type": "System.Int32", - "IsRequired": true - }, - { - "Name": "adjustment", - "Type": "Domain.Entities.EQuoteAdjustment", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "GetTaxes", - "RelativePath": "api/Quote/{quoteId}/taxes", - "HttpMethod": "GET", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "quoteId", - "Type": "System.Int32", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "AddTax", - "RelativePath": "api/Quote/{quoteId}/taxes", - "HttpMethod": "POST", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "quoteId", - "Type": "System.Int32", - "IsRequired": true - }, - { - "Name": "tax", - "Type": "Domain.Entities.EQuoteTax", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "UpdateAdjustment", - "RelativePath": "api/Quote/adjustments", - "HttpMethod": "PUT", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "adjustment", - "Type": "Domain.Entities.EQuoteAdjustment", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "DeleteAdjustment", - "RelativePath": "api/Quote/adjustments/{adjustmentId}", - "HttpMethod": "DELETE", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "adjustmentId", - "Type": "System.Int32", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, { "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "Method": "GetAll", @@ -1683,27 +1577,6 @@ ], "ReturnTypes": [] }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "Create", - "RelativePath": "api/Quote/create", - "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": "CreateFullQuote", @@ -1713,14 +1586,9 @@ "Order": 0, "Parameters": [ { - "Name": "quote", - "Type": "Domain.Entities.EQuoteHeader", + "Name": "request", + "Type": "phronCare.API.Controllers.Sales.QuoteController\u002BCreateFullQuoteRequest", "IsRequired": true - }, - { - "Name": "formSeriesId", - "Type": "System.Int32", - "IsRequired": false } ], "ReturnTypes": [] @@ -1818,38 +1686,6 @@ ], "ReturnTypes": [] }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "UpdateTax", - "RelativePath": "api/Quote/taxes", - "HttpMethod": "PUT", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "tax", - "Type": "Domain.Entities.EQuoteTax", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, - { - "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", - "Method": "DeleteTax", - "RelativePath": "api/Quote/taxes/{taxId}", - "HttpMethod": "DELETE", - "IsController": true, - "Order": 0, - "Parameters": [ - { - "Name": "taxId", - "Type": "System.Int32", - "IsRequired": true - } - ], - "ReturnTypes": [] - }, { "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "Method": "Update", diff --git a/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor index 3c5c869..5ecec92 100644 --- a/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor +++ b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor @@ -4,10 +4,14 @@ @using Blazored.Typeahead @using Services.Lookups @using phronCare.UIBlazor.Pages.Sales.Modals +@using phronCare.UIBlazor.Services.Sales.Quotes @inject ISalesLookupService SalesLookupService +@inject IQuoteService QuoteService +@inject IToastService toastService +@inject NavigationManager Navigation @inject IModalService Modal - +
@@ -110,9 +114,11 @@
+ +
@@ -296,7 +302,7 @@
@@ -369,12 +375,12 @@ => SetLookupSelection(item, sel => _selectedCustomer = sel, id => _quoteModel.CustomerId = id); private Task OnPersonSelected(ELookUpItem item) => SetLookupSelection(item, sel => _selectedPerson = sel, id => _quoteModel.PeopleId = id); - private Task OnProfessionalSelected(ELookUpItem item) - => SetLookupSelection(item, sel => _selectedProfessional = sel); - private Task OnInstitutionSelected(ELookUpItem item) - => SetLookupSelection(item, sel => _selectedInstitution = sel); - private Task OnPatientSelected(ELookUpItem item) - => SetLookupSelection(item, sel => _selectedPatient = sel); + // private Task OnProfessionalSelected(ELookUpItem item) + // => SetLookupSelection(item, sel => _selectedProfessional = sel); + // private Task OnInstitutionSelected(ELookUpItem item) + // => SetLookupSelection(item, sel => _selectedInstitution = sel); + // private Task OnPatientSelected(ELookUpItem item) + // => SetLookupSelection(item, sel => _selectedPatient = sel); private Task SetLookupSelection(ELookUpItem? item, Action setSelected, Action? setModelId = null) { setSelected(item); @@ -439,4 +445,65 @@ _quoteModel.PhSQuoteTaxes.Remove(tax); RecalculateTotals(); } + + private async Task HandleValidSubmit() + { + // Si necesitas validar algo extra antes de llamar al servicio, hazlo aquí + int selectedSeriesId = 1/* obtenlo de donde corresponda, p.ej. de un dropdown */; + + var result = await QuoteService.CreateFullQuoteAsync(_quoteModel, selectedSeriesId); + if (!result.Success) + { + toastService.ShowError(result.ErrorMessage); + return; + } + + toastService.ShowSuccess($"Presupuesto creado: {result.QuoteNumber}"); + //Navigation.NavigateTo($"/sales/quotes/details/{result.QuoteNumber}"); + } + + /// + /// Agrega o actualiza un rol dentro de _quoteModel.PhSQuoteRoles + /// + private void AddOrUpdateRole(string entityType, int entityId, string roleName) + { + var existing = _quoteModel.PhSQuoteRoles + .FirstOrDefault(r => r.Entitytype == entityType); + if (existing != null) + { + existing.EntityId = entityId; + existing.Role = roleName; + } + else + { + _quoteModel.PhSQuoteRoles.Add(new EQuoteRole + { + // QuoteheaderId lo llenará EF en el servidor + Entitytype = entityType, + EntityId = entityId, + Role = roleName + }); + } + } + private Task OnProfessionalSelected(ELookUpItem item) + { + _selectedProfessional = item; + AddOrUpdateRole("PhS_Professionals", item.Id, "Medico"); + return Task.CompletedTask; + } + + private Task OnInstitutionSelected(ELookUpItem item) + { + _selectedInstitution = item; + AddOrUpdateRole("PhS_Institutions", item.Id, "Hospital"); + return Task.CompletedTask; + } + + private Task OnPatientSelected(ELookUpItem item) + { + _selectedPatient = item; + AddOrUpdateRole("PhS_Patients", item.Id, "Paciente"); + return Task.CompletedTask; + } + } diff --git a/phronCare.UIBlazor/Program.cs b/phronCare.UIBlazor/Program.cs index 3274ca4..da69228 100644 --- a/phronCare.UIBlazor/Program.cs +++ b/phronCare.UIBlazor/Program.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Blazored.Modal; using Blazored.Toast; +using phronCare.UIBlazor.Services.Sales.Quotes; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -52,6 +53,7 @@ await builder.Build().RunAsync(); static void InjectDependencies(WebAssemblyHostBuilder builder) { builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -66,4 +68,5 @@ static void InjectDependencies(WebAssemblyHostBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + } \ No newline at end of file diff --git a/phronCare.UIBlazor/Services/Sales/Quotes/IQuoteService.cs b/phronCare.UIBlazor/Services/Sales/Quotes/IQuoteService.cs new file mode 100644 index 0000000..4b46209 --- /dev/null +++ b/phronCare.UIBlazor/Services/Sales/Quotes/IQuoteService.cs @@ -0,0 +1,10 @@ +using Domain.Entities; + +namespace phronCare.UIBlazor.Services.Sales.Quotes +{ + public interface IQuoteService + { + Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId); + // Aquí podrías agregar otros métodos: GetById, Search, etc. + } +} diff --git a/phronCare.UIBlazor/Services/Sales/Quotes/QuoteService.cs b/phronCare.UIBlazor/Services/Sales/Quotes/QuoteService.cs new file mode 100644 index 0000000..2cad7cd --- /dev/null +++ b/phronCare.UIBlazor/Services/Sales/Quotes/QuoteService.cs @@ -0,0 +1,62 @@ +using Domain.Entities; +using System.Net.Http.Json; + +namespace phronCare.UIBlazor.Services.Sales.Quotes +{ + public class QuoteService : IQuoteService + { + private readonly HttpClient _http; + + public QuoteService(HttpClient http) + { + _http = http; + } + + /// + /// Envía el EQuoteHeader junto con el formSeriesId al backend + /// y recibe el número de presupuesto generado o un error. + /// + public async Task CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId) + { + // 1) Creamos el DTO request tal cual lo espera el controller + var request = new CreateFullQuoteRequest + { + Quote = quote, + FormSeriesId = formSeriesId + }; + + // 2) Enviamos ese DTO al endpoint + var response = await _http.PostAsJsonAsync("/api/quote/createfull", request); + //var url = $"/api/quote/createfull?formSeriesId={formSeriesId}"; + //var response = await _http.PostAsJsonAsync(url, quote); + + + if (!response.IsSuccessStatusCode) + { + // Leer mensaje de error del servidor + var serverMessage = await response.Content.ReadAsStringAsync(); + return new CreateQuoteResult + { + Success = false, + ErrorMessage = serverMessage + }; + } + + // Deserializar el resultado esperado + var result = await response.Content.ReadFromJsonAsync(); + return result!; + } + } + public class CreateQuoteResult + { + public bool Success { get; set; } + public string QuoteNumber { get; set; } = string.Empty; + public string ErrorMessage { get; set; } = string.Empty; + } + public class CreateFullQuoteRequest + { + public EQuoteHeader Quote { get; set; } = default!; + public int FormSeriesId { get; set; } + } + +}