diff --git a/Core/Interfaces/IQuoteDom.cs b/Core/Interfaces/IQuoteDom.cs new file mode 100644 index 0000000..29b08c1 --- /dev/null +++ b/Core/Interfaces/IQuoteDom.cs @@ -0,0 +1,21 @@ +using Domain.Entities; +using Domain.Generics; + +namespace Models.Interfaces +{ + public interface IQuoteDom + { + Task> GetAllQuotesAsync(int page = 1, int pageSize = 50); + Task GetQuoteByIdAsync(int id); + Task> GetQuotesByCustomerAsync(int customerId); + Task> SearchQuotesAsync(int? customerId, + string? quoteNumber,int? professionalId, int? institutionId, + int? patientId, DateTime? issueDateFrom,DateTime? issueDateTo, + string? status, int page = 1, int pageSize = 50); + + Task CreateQuoteAsync(EQuoteHeader quote, int formSeriesId); + Task UpdateQuoteAsync(EQuoteHeader quote); + Task DeleteQuoteAsync(int id); + Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams); + } +} diff --git a/Core/Services/QuoteService.cs b/Core/Services/QuoteService.cs new file mode 100644 index 0000000..2c02044 --- /dev/null +++ b/Core/Services/QuoteService.cs @@ -0,0 +1,151 @@ +using Domain.Entities; +using Domain.Generics; +using Models.Interfaces; +using System.Reflection; +using Transversal.Services; + +namespace PhronCare.Core.Services.Sales +{ + public class QuoteService( + IPhSQuoteHeaderRepository quoteHeaderRepository, + IPhSQuoteDetailRepository quoteDetailRepository, + IPhSQuoteRoleRepository quoteRoleRepository, + IPhSFormSeriesRepository formSeriesRepository + ) : IQuoteDom + { + #region Declaraciones + private readonly IPhSQuoteHeaderRepository _quoteHeaderRepository = quoteHeaderRepository; + private readonly IPhSQuoteDetailRepository _quoteDetailRepository = quoteDetailRepository; + private readonly IPhSQuoteRoleRepository _quoteRoleRepository = quoteRoleRepository; + private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository; + #endregion + + #region Métodos + public async Task> GetAllQuotesAsync(int page = 1, int pageSize = 50) + { + return await _quoteHeaderRepository.GetAllAsync(page, pageSize); + } + + public async Task GetQuoteByIdAsync(int id) + { + return await _quoteHeaderRepository.GetByIdAsync(id); + } + + public async Task> GetQuotesByCustomerAsync(int customerId) + { + var quotes = await _quoteHeaderRepository.GetByCustomerIdAsync(customerId); + return quotes; + } + public async Task> SearchQuotesAsync(int? customerId, string? quoteNumber, int? professionalId, int? institutionId, int? patientId, DateTime? issueDateFrom, DateTime? issueDateTo, string? status, int page = 1, int pageSize = 50) + { + return await _quoteHeaderRepository.SearchAsync(customerId, + quoteNumber, + professionalId, + institutionId, + patientId, + issueDateFrom, + issueDateTo, + status, + page, + pageSize); + } + + public async Task CreateQuoteAsync(EQuoteHeader quote, int formSeriesId) + { + // Obtener el próximo número de documento + var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId); + + // Asignar el número al presupuesto + quote.Quotenumber = nextNumber.ToString(); + + // Crear el presupuesto + var newQuote = await _quoteHeaderRepository.AddAsync(quote); + + // Crear los detalles asociados + if (quote.Details != null) + { + foreach (var detail in quote.Details) + { + detail.QuoteheaderId = newQuote.Id; + await _quoteDetailRepository.AddAsync(detail); + } + } + + // Crear los roles asociados + if (quote.Roles != null) + { + foreach (var role in quote.Roles) + { + 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); + } + + public async Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams) + { + try + { + // Buscar los presupuestos con los filtros indicados + var searchResult = await SearchQuotesAsync( + searchParams.CustomerId, + searchParams.QuoteNumber, + searchParams.ProfessionalId, + searchParams.InstitutionId, + searchParams.PatientId, + searchParams.IssueDateFrom, + searchParams.IssueDateTo, + searchParams.Status, + searchParams.Page, + searchParams.PageSize + ); + + // Verificar si hay resultados + if (searchResult?.Items == null || !searchResult.Items.Any()) + { + throw new Exception("No se encontraron presupuestos para exportar."); + } + + // Instanciar exportador + var stream = new XLSXExportBase(); + + // Armar los datos a exportar + var quotesData = searchResult.Items.Select(q => new + { + NúmeroPresupuesto = q.Quotenumber, + Estado = q.Status, + FechaEmisión = q.Issuedate.ToString("yyyy-MM-dd"), + FechaTentativa = q.Estimateddate?.ToString("yyyy-MM-dd"), + ImporteEstimado = q.Estimatedamount, + ImporteAprobado = q.Approvedamount, + Profesional = q.Roles.FirstOrDefault(r => r.Entitytype == "PhS_Professionals")?.Entitytype, + Institución = q.Roles.FirstOrDefault(r => r.Entitytype == "PhS_Institutions")?.Entitytype, + Paciente = q.Roles.FirstOrDefault(r => r.Entitytype == "PhS_Patients")?.Entitytype + }).ToList(); + + // Generar archivo Excel + var excelFile = stream.ExportExcel(quotesData); + + return excelFile; + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + throw new Exception($"{methodName} Message: {ex.Message}", ex); + } + } + #endregion + } +} diff --git a/Domain/Entities/EQuoteDetail.cs b/Domain/Entities/EQuoteDetail.cs new file mode 100644 index 0000000..3d874c3 --- /dev/null +++ b/Domain/Entities/EQuoteDetail.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Domain.Entities +{ + /// + /// Tabla de detalles de presupuestos + /// + public class EQuoteDetail + { + /// + /// ID interno + /// + public int Id { get; set; } + + /// + /// FK a encabezado de presupuesto + /// + public int QuoteheaderId { get; set; } + + /// + /// ID del producto original + /// + public int ProductId { get; set; } + + /// + /// Descripción modificable del producto (puede diferir del original) + /// + public string? ProductDescription { get; set; } + + /// + /// Cantidad + /// + public int Quantity { get; set; } + + /// + /// Precio unitario original + /// + public decimal Unitprice { get; set; } + + /// + /// ¿Aprobado? (0=No, 1=Si) + /// + public bool Approved { get; set; } + + /// + /// Importe aprobado final + /// + public decimal? Approvedamount { get; set; } + + /// + /// Fecha de creación del registro + /// + public DateTime Createdat { get; set; } + + /// + /// Fecha de última modificación + /// + public DateTime? Modifiedat { get; set; } + + public virtual EQuoteHeader Quoteheader { get; set; } = null!; + } +} diff --git a/Domain/Entities/EQuoteHeader.cs b/Domain/Entities/EQuoteHeader.cs new file mode 100644 index 0000000..4ef9c1a --- /dev/null +++ b/Domain/Entities/EQuoteHeader.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Domain.Entities +{ + /// + /// Tabla de cabeceras de presupuestos + /// + public class EQuoteHeader + { + /// + /// ID interno + /// + public int Id { get; set; } + + /// + /// Relación con Tickets + /// + public Guid TicketId { get; set; } + + /// + /// Cliente asociado + /// + public int CustomerId { get; set; } + + /// + /// Unidad de negocio + /// + public int BusinessunitId { get; set; } + + /// + /// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc. + /// + public string Status { get; set; } = null!; + + /// + /// Fecha de emisión + /// + public DateTime Issuedate { get; set; } + + /// + /// Fecha de aprobación + /// + public DateOnly? Approvaldate { get; set; } + + /// + /// Fecha tentativa (de cirugía por ej.) + /// + public DateTime? Estimateddate { get; set; } + + /// + /// Importe estimado total + /// + public decimal? Estimatedamount { get; set; } + + /// + /// Importe aprobado + /// + public decimal? Approvedamount { get; set; } + + /// + /// Número visible del presupuesto + /// + public string Quotenumber { get; set; } = null!; + + /// + /// Cantidad de impresiones + /// + public int Printcount { get; set; } + + /// + /// Observaciones internas + /// + public string? Observations { get; set; } + + /// + /// Fecha de creación + /// + public DateTime Createdat { get; set; } + + /// + /// Fecha de modificación + /// + public DateTime? Modifiedat { get; set; } + + public virtual ICollection Details { get; set; } = new List(); + + public virtual ICollection Roles { get; set; } = new List(); + } +} diff --git a/Domain/Entities/EQuoteRole.cs b/Domain/Entities/EQuoteRole.cs new file mode 100644 index 0000000..211f042 --- /dev/null +++ b/Domain/Entities/EQuoteRole.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Domain.Entities +{ + public class EQuoteRole + { + /// + /// ID interno + /// + public int Id { get; set; } + + /// + /// FK a encabezado de presupuesto (PhS_QuoteHeaders) + /// + public int QuoteheaderId { get; set; } + + /// + /// Tipo de entidad asociada (Ej: PhS_Professionals, PhS_Institutions, PhS_Patients) + /// + public string Entitytype { get; set; } = null!; + + /// + /// ID de la entidad asociada + /// + public int EntityId { get; set; } + + /// + /// Rol que cumple la entidad en el presupuesto (Ej: Medico, Hospital, Paciente) + /// + public string Role { get; set; } = null!; + } +} diff --git a/Domain/Generics/QuoteSearchParams.cs b/Domain/Generics/QuoteSearchParams.cs new file mode 100644 index 0000000..606b179 --- /dev/null +++ b/Domain/Generics/QuoteSearchParams.cs @@ -0,0 +1,14 @@ +namespace Domain.Generics +{ + public class QuoteSearchParams : PagedRequest + { + public int? CustomerId { get; set; } + public string? QuoteNumber { get; set; } + public int? ProfessionalId { get; set; } + public int? InstitutionId { get; set; } + public int? PatientId { get; set; } + public DateTime? IssueDateFrom { get; set; } + public DateTime? IssueDateTo { get; set; } + public string? Status { get; set; } + } +} diff --git a/Models/Interfaces/IPhSFormSeriesRepository.cs b/Models/Interfaces/IPhSFormSeriesRepository.cs new file mode 100644 index 0000000..ba8584e --- /dev/null +++ b/Models/Interfaces/IPhSFormSeriesRepository.cs @@ -0,0 +1,7 @@ +namespace Models.Interfaces +{ + public interface IPhSFormSeriesRepository + { + Task GetNextInternalNumberAsync(int formSeriesId); + } +} diff --git a/Models/Interfaces/IPhSQuoteDetailRepository.cs b/Models/Interfaces/IPhSQuoteDetailRepository.cs new file mode 100644 index 0000000..3ad665f --- /dev/null +++ b/Models/Interfaces/IPhSQuoteDetailRepository.cs @@ -0,0 +1,14 @@ +using Domain.Entities; +using Domain.Generics; + +namespace Models.Interfaces +{ + public interface IPhSQuoteDetailRepository + { + Task AddAsync(EQuoteDetail quoteDetail); + Task DeleteAsync(int id); + Task> GetAllAsync(int page = 1, int pageSize = 50); + Task GetByIdAsync(int id); + Task UpdateAsync(EQuoteDetail quoteDetail); + } +} diff --git a/Models/Interfaces/IPhSQuoteHeaderRepository.cs b/Models/Interfaces/IPhSQuoteHeaderRepository.cs new file mode 100644 index 0000000..4fd1cb2 --- /dev/null +++ b/Models/Interfaces/IPhSQuoteHeaderRepository.cs @@ -0,0 +1,16 @@ +using Domain.Entities; +using Domain.Generics; + +namespace Models.Interfaces +{ + public interface IPhSQuoteHeaderRepository + { + Task> GetAllAsync(int page = 1, int pageSize = 50); + Task> GetByCustomerIdAsync(int customerId); + Task GetByIdAsync(int id); + 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); + } +} diff --git a/Models/Interfaces/IPhSQuoteRoleRepository.cs b/Models/Interfaces/IPhSQuoteRoleRepository.cs new file mode 100644 index 0000000..b17711d --- /dev/null +++ b/Models/Interfaces/IPhSQuoteRoleRepository.cs @@ -0,0 +1,14 @@ +using Domain.Entities; +using Domain.Generics; + +namespace Models.Interfaces +{ + public interface IPhSQuoteRoleRepository + { + Task AddAsync(EQuoteRole quoteRole); + Task DeleteAsync(int id); + Task> GetAllAsync(int page = 1, int pageSize = 50); + Task GetByIdAsync(int id); + Task UpdateAsync(EQuoteRole quoteRole); + } +} diff --git a/Models/Repositories/PhSFormSeriesRepository.cs b/Models/Repositories/PhSFormSeriesRepository.cs new file mode 100644 index 0000000..035165d --- /dev/null +++ b/Models/Repositories/PhSFormSeriesRepository.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore; +using Models.Interfaces; +using Models.Models; + +namespace PhronCare.Core.Data.Repositories.Sales +{ + public class PhSFormSeriesRepository(PhronCareOperationsHubContext context) : IPhSFormSeriesRepository + { + #region Declaraciones + private readonly PhronCareOperationsHubContext _context = context; + #endregion + + #region Métodos + + public async Task GetNextInternalNumberAsync(int formSeriesId) + { + var nextNumberParam = new Microsoft.Data.SqlClient.SqlParameter + { + ParameterName = "@NextNumber", + SqlDbType = System.Data.SqlDbType.Int, + Direction = System.Data.ParameterDirection.Output + }; + + await _context.Database.ExecuteSqlRawAsync( + "EXEC dbo.PhS_FormSeries_GetNextNumber @FormSeriesId = {0}, @NextNumber = @NextNumber OUTPUT", + formSeriesId, + nextNumberParam + ); + + return (int)nextNumberParam.Value; + } + + #endregion + } +} diff --git a/Models/Repositories/PhSQuoteDetailRepository.cs b/Models/Repositories/PhSQuoteDetailRepository.cs new file mode 100644 index 0000000..d34dcc2 --- /dev/null +++ b/Models/Repositories/PhSQuoteDetailRepository.cs @@ -0,0 +1,63 @@ +using Domain.Entities; +using Domain.Generics; +using Microsoft.EntityFrameworkCore; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace PhronCare.Core.Data.Repositories.Sales +{ + public class PhSQuoteDetailRepository(PhronCareOperationsHubContext context) : IPhSQuoteDetailRepository + { + #region Declaraciones + private readonly PhronCareOperationsHubContext _context = context; + #endregion + + #region Métodos + public async Task> GetAllAsync(int page = 1, int pageSize = 50) + { + var query = _context.PhSQuoteDetails + .AsQueryable(); + + var pagedEntities = await query.ToPagedResultAsync(page, pageSize); + + return new PagedResult + { + Items = pagedEntities.Items.Select(EntityMapper.MapEntity), + TotalItems = pagedEntities.TotalItems, + Page = pagedEntities.Page, + PageSize = pagedEntities.PageSize + }; + } + public async Task GetByIdAsync(int id) + { + var entity = await _context.PhSQuoteDetails + .FirstOrDefaultAsync(q => q.Id == id); + + return entity != null ? EntityMapper.MapEntity(entity) : null; + } + public async Task AddAsync(EQuoteDetail quoteDetail) + { + var dbEntity = EntityMapper.MapEntity(quoteDetail); + _context.PhSQuoteDetails.Add(dbEntity); + await _context.SaveChangesAsync(); + return EntityMapper.MapEntity(dbEntity); + } + public async Task UpdateAsync(EQuoteDetail quoteDetail) + { + var dbEntity = EntityMapper.MapEntity(quoteDetail); + _context.PhSQuoteDetails.Update(dbEntity); + await _context.SaveChangesAsync(); + } + public async Task DeleteAsync(int id) + { + var entity = await _context.PhSQuoteDetails.FindAsync(id); + if (entity != null) + { + _context.PhSQuoteDetails.Remove(entity); + await _context.SaveChangesAsync(); + } + } + #endregion + } +} diff --git a/Models/Repositories/PhSQuoteHeaderRepository.cs b/Models/Repositories/PhSQuoteHeaderRepository.cs new file mode 100644 index 0000000..deccea2 --- /dev/null +++ b/Models/Repositories/PhSQuoteHeaderRepository.cs @@ -0,0 +1,137 @@ +using Domain.Entities; +using Domain.Generics; +using Microsoft.EntityFrameworkCore; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace PhronCare.Core.Data.Repositories.Sales +{ + public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context): IPhSQuoteHeaderRepository + { + #region Declaraciones + private readonly PhronCareOperationsHubContext _context = context; + #endregion + #region Métodos + public async Task> GetAllAsync(int page = 1, int pageSize = 50) + { + var query = _context.PhSQuoteHeaders + .Include(q => q.PhSQuoteDetails) + .Include(q => q.PhSQuoteRoles) + .AsQueryable(); + + var pagedEntities = await query.ToPagedResultAsync(page, pageSize); + + return new PagedResult + { + Items = pagedEntities.Items.Select(EntityMapper.MapEntity), + TotalItems = pagedEntities.TotalItems, + Page = pagedEntities.Page, + PageSize = pagedEntities.PageSize + }; + } + public async Task GetByIdAsync(int id) + { + var entity = await _context.PhSQuoteHeaders + .Include(q => q.PhSQuoteDetails) + .Include(q => q.PhSQuoteRoles) + .FirstOrDefaultAsync(q => q.Id == id); + + return entity != null ? EntityMapper.MapEntity(entity) : null; + } + public async Task> GetByCustomerIdAsync(int customerId) + { + var entities = await _context.PhSQuoteHeaders + .Where(q => q.CustomerId == customerId) + .Include(q => q.PhSQuoteDetails) + .Include(q => q.PhSQuoteRoles) + .ToListAsync(); + + return entities.Select(EntityMapper.MapEntity); + } + public async Task> SearchAsync(int? customerId, + string? quoteNumber, int? professionalId, int? institutionId, int? patientId, + DateTime? issueDateFrom, DateTime? issueDateTo, + string? status, int page = 1, int pageSize = 50) + { + var query = _context.PhSQuoteHeaders + .Include(q => q.PhSQuoteDetails) + .Include(q => q.PhSQuoteRoles) + .AsQueryable(); + + if (customerId.HasValue) + { + query = query.Where(q => q.CustomerId == customerId); + } + + if (!string.IsNullOrEmpty(quoteNumber)) + { + query = query.Where(q => q.Quotenumber.Contains(quoteNumber)); + } + + if (professionalId.HasValue) + { + query = query.Where(q => q.PhSQuoteRoles.Any(r => r.Entitytype == "PhS_Professionals" && r.EntityId == professionalId)); + } + + if (institutionId.HasValue) + { + query = query.Where(q => q.PhSQuoteRoles.Any(r => r.Entitytype == "PhS_Institutions" && r.EntityId == institutionId)); + } + + if (patientId.HasValue) + { + query = query.Where(q => q.PhSQuoteRoles.Any(r => r.Entitytype == "PhS_Patients" && r.EntityId == patientId)); + } + + if (issueDateFrom.HasValue) + { + query = query.Where(q => q.Issuedate >= issueDateFrom.Value); + } + + if (issueDateTo.HasValue) + { + query = query.Where(q => q.Issuedate <= issueDateTo.Value); + } + + if (!string.IsNullOrEmpty(status)) + { + query = query.Where(q => q.Status == status); + } + + // Paginación final + var pagedEntities = await query.ToPagedResultAsync(page, pageSize); + + return new PagedResult + { + Items = pagedEntities.Items.Select(EntityMapper.MapEntity), + TotalItems = pagedEntities.TotalItems, + Page = pagedEntities.Page, + 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); + _context.PhSQuoteHeaders.Update(dbEntity); + await _context.SaveChangesAsync(); + } + public async Task DeleteAsync(int id) + { + var entity = await _context.PhSQuoteHeaders.FindAsync(id); + if (entity != null) + { + _context.PhSQuoteHeaders.Remove(entity); + await _context.SaveChangesAsync(); + } + } + #endregion + } +} diff --git a/Models/Repositories/PhSQuoteRoleRepository.cs b/Models/Repositories/PhSQuoteRoleRepository.cs new file mode 100644 index 0000000..9e026cf --- /dev/null +++ b/Models/Repositories/PhSQuoteRoleRepository.cs @@ -0,0 +1,62 @@ +using Domain.Entities; +using Domain.Generics; +using Microsoft.EntityFrameworkCore; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace PhronCare.Core.Data.Repositories.Sales +{ + public class PhSQuoteRoleRepository(PhronCareOperationsHubContext context) : IPhSQuoteRoleRepository + { + #region Declaraciones + private readonly PhronCareOperationsHubContext _context = context; + #endregion + #region Métodos + public async Task> GetAllAsync(int page = 1, int pageSize = 50) + { + var query = _context.PhSQuoteRoles + .AsQueryable(); + + var pagedEntities = await query.ToPagedResultAsync(page, pageSize); + + return new PagedResult + { + Items = pagedEntities.Items.Select(EntityMapper.MapEntity), + TotalItems = pagedEntities.TotalItems, + Page = pagedEntities.Page, + PageSize = pagedEntities.PageSize + }; + } + public async Task GetByIdAsync(int id) + { + var entity = await _context.PhSQuoteRoles + .FirstOrDefaultAsync(q => q.Id == id); + + return entity != null ? EntityMapper.MapEntity(entity) : null; + } + public async Task AddAsync(EQuoteRole quoteRole) + { + var dbEntity = EntityMapper.MapEntity(quoteRole); + _context.PhSQuoteRoles.Add(dbEntity); + await _context.SaveChangesAsync(); + return EntityMapper.MapEntity(dbEntity); + } + public async Task UpdateAsync(EQuoteRole quoteRole) + { + var dbEntity = EntityMapper.MapEntity(quoteRole); + _context.PhSQuoteRoles.Update(dbEntity); + await _context.SaveChangesAsync(); + } + public async Task DeleteAsync(int id) + { + var entity = await _context.PhSQuoteRoles.FindAsync(id); + if (entity != null) + { + _context.PhSQuoteRoles.Remove(entity); + await _context.SaveChangesAsync(); + } + } + #endregion + } +} diff --git a/phronCare.API/Controllers/Sales/QuoteController.cs b/phronCare.API/Controllers/Sales/QuoteController.cs new file mode 100644 index 0000000..47d59c2 --- /dev/null +++ b/phronCare.API/Controllers/Sales/QuoteController.cs @@ -0,0 +1,165 @@ +using Core.Interfaces; +using Domain.Entities; +using Domain.Generics; +using Microsoft.AspNetCore.Mvc; +using Models.Interfaces; +using System.Reflection; + +namespace phronCare.API.Controllers.Sales +{ + [Route("api/[controller]")] + [ApiController] + public class QuoteController : ControllerBase + { + private readonly IQuoteDom _quoteService; + + public QuoteController(IQuoteDom quoteService) + { + _quoteService = quoteService ?? throw new ArgumentNullException(nameof(quoteService)); + } + + [HttpGet("all")] + public async Task GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50) + { + try + { + var result = await _quoteService.GetAllQuotesAsync(page, pageSize); + return Ok(result); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpGet("search")] + public async Task Search( + [FromQuery] int? customerId, + [FromQuery] string? quoteNumber, + [FromQuery] int? professionalId, + [FromQuery] int? institutionId, + [FromQuery] int? patientId, + [FromQuery] DateTime? issueDateFrom, + [FromQuery] DateTime? issueDateTo, + [FromQuery] string? status, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 50) + { + try + { + var result = await _quoteService.SearchQuotesAsync( + customerId, + quoteNumber, + professionalId, + institutionId, + patientId, + issueDateFrom, + issueDateTo, + status, + page, + pageSize); + + return Ok(result); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpGet("{id:int}")] + public async Task> GetById(int id) + { + try + { + var quote = await _quoteService.GetQuoteByIdAsync(id); + if (quote == null) + return NotFound(); + + return Ok(quote); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [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) + { + try + { + if (quote == null || quote.Id <= 0) + return BadRequest("El presupuesto es inválido o no tiene un ID válido."); + + await _quoteService.UpdateQuoteAsync(quote); + return Ok("Presupuesto actualizado correctamente."); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpDelete("delete/{id:int}")] + public async Task Delete(int id) + { + try + { + await _quoteService.DeleteQuoteAsync(id); + return Ok("Presupuesto eliminado correctamente."); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + [HttpPost("exportfiltered")] + public async Task ExportFiltered([FromBody] QuoteSearchParams searchParams) + { + try + { + var file = await _quoteService.ExportFilteredQuotesToExcelAsync(searchParams); + return File(file, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Presupuestos.xlsx"); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + } +} diff --git a/phronCare.API/Program.cs b/phronCare.API/Program.cs index 216a01e..862c086 100644 --- a/phronCare.API/Program.cs +++ b/phronCare.API/Program.cs @@ -15,6 +15,8 @@ using Services.Services; using Services.Interfaces; using System.Text; using Infrastructure.Repositories.Patients; +using PhronCare.Core.Services.Sales; +using PhronCare.Core.Data.Repositories.Sales; var builder = WebApplication.CreateBuilder(args); @@ -70,6 +72,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + #endregion #region Require Confirmed Email diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json index 7d38dfd..b734fe5 100644 --- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json +++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json @@ -1206,6 +1206,183 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "GetById", + "RelativePath": "api/Quote/{id}", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "id", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [ + { + "Type": "Domain.Entities.EQuoteHeader", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "GetAll", + "RelativePath": "api/Quote/all", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "page", + "Type": "System.Int32", + "IsRequired": false + }, + { + "Name": "pageSize", + "Type": "System.Int32", + "IsRequired": false + } + ], + "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": "Delete", + "RelativePath": "api/Quote/delete/{id}", + "HttpMethod": "DELETE", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "id", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "ExportFiltered", + "RelativePath": "api/Quote/exportfiltered", + "HttpMethod": "POST", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "searchParams", + "Type": "Domain.Generics.QuoteSearchParams", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "Search", + "RelativePath": "api/Quote/search", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "customerId", + "Type": "System.Nullable\u00601[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + "IsRequired": false + }, + { + "Name": "quoteNumber", + "Type": "System.String", + "IsRequired": false + }, + { + "Name": "professionalId", + "Type": "System.Nullable\u00601[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + "IsRequired": false + }, + { + "Name": "institutionId", + "Type": "System.Nullable\u00601[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + "IsRequired": false + }, + { + "Name": "patientId", + "Type": "System.Nullable\u00601[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + "IsRequired": false + }, + { + "Name": "issueDateFrom", + "Type": "System.Nullable\u00601[[System.DateTime, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + "IsRequired": false + }, + { + "Name": "issueDateTo", + "Type": "System.Nullable\u00601[[System.DateTime, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", + "IsRequired": false + }, + { + "Name": "status", + "Type": "System.String", + "IsRequired": false + }, + { + "Name": "page", + "Type": "System.Int32", + "IsRequired": false + }, + { + "Name": "pageSize", + "Type": "System.Int32", + "IsRequired": false + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "Update", + "RelativePath": "api/Quote/update", + "HttpMethod": "PUT", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "quote", + "Type": "Domain.Entities.EQuoteHeader", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.Sales.TaxConditionController", "Method": "GetAll",