diff --git a/Core/Interfaces/IPeopleDom.cs b/Core/Interfaces/IPeopleDom.cs new file mode 100644 index 0000000..7961657 --- /dev/null +++ b/Core/Interfaces/IPeopleDom.cs @@ -0,0 +1,16 @@ +using Domain.Entities; +using Domain.Generics; + +namespace Models.Interfaces +{ + public interface IPeopleDom + { + Task> GetAllAsync(int page = 1, int pageSize = 50); + Task> SearchAsync(string? name, string? email, int page = 1, int pageSize = 50); + Task GetByIdAsync(int id); + Task CreateAsync(EPerson person); + Task UpdateAsync(EPerson person); + Task DeleteAsync(int id); + Task ExportFilteredCustomersToExcelAsync(PeopleSearchParams searchParams); + } +} diff --git a/Core/Services/PatientService.cs b/Core/Services/PatientService.cs index fc34ef4..7293ab5 100644 --- a/Core/Services/PatientService.cs +++ b/Core/Services/PatientService.cs @@ -66,7 +66,6 @@ public class PatientService : IPatientDom return await _repository.UpdateAsync(entity); } - public async Task> SearchAsync(string? name, string? document, int page = 1, int pageSize = 50) { try @@ -79,7 +78,6 @@ public class PatientService : IPatientDom throw new Exception($"{methodName} Message: {ex.Message}", ex); } } - public Task DeleteAsync(int id) { // Implementar según políticas del sistema (soft delete, etc.) @@ -125,6 +123,5 @@ public class PatientService : IPatientDom throw new Exception($"{methodName} - Error al exportar pacientes: {ex.Message}", ex); } } - #endregion } diff --git a/Core/Services/PeopleService.cs b/Core/Services/PeopleService.cs new file mode 100644 index 0000000..0e84ab8 --- /dev/null +++ b/Core/Services/PeopleService.cs @@ -0,0 +1,98 @@ +using Domain.Entities; +using Domain.Generics; +using Models.Interfaces; +using System.Reflection; +using Transversal.Services; + +namespace Core.Services +{ + public class PeopleService : IPeopleDom + { + #region Declaraciones + private readonly IPhSPeopleRepository _peopleRepository ; + #endregion + public PeopleService(IPhSPeopleRepository peopleRepository) + { + _peopleRepository = peopleRepository ?? throw new ArgumentNullException(nameof(peopleRepository)); + } + #region Métodos + public async Task> GetAllAsync(int page = 1, int pageSize = 50) + { + return await _peopleRepository.GetAllAsync(page, pageSize); + } + public async Task> SearchAsync(string? name, string? email, + int page = 1, int pageSize = 50) + { + return await _peopleRepository.SearchAsync(name,email,page,pageSize); + } + public async Task GetByIdAsync(int id) + { + return await _peopleRepository.GetByIdAsync(id); + } + + public async Task CreateAsync(EPerson person) + { + return await _peopleRepository.AddAsync(person); + } + + public async Task UpdateAsync(EPerson person) + { + var existing = await _peopleRepository.GetByIdAsync(person.Id); + if (existing == null) + return false; + + await _peopleRepository.UpdateAsync(person); + return true; + } + + public async Task DeleteAsync(int id) + { + var existing = await _peopleRepository.GetByIdAsync(id); + if (existing == null) + return false; + + await _peopleRepository.DeleteAsync(id); + return true; + } + public async Task ExportFilteredCustomersToExcelAsync(PeopleSearchParams searchParams) + { + try + { + // Realiza la búsqueda de clientes con los parámetros proporcionados + var searchResult = await SearchAsync( + searchParams.Name, + searchParams.Email, + searchParams.Page, + searchParams.PageSize + ); + // Verifica que se hayan encontrado resultados + if (searchResult?.Items is null || !searchResult.Items.Any()) + { + throw new Exception("No se encontraron clientes para exportar."); + } + // Llamamos a un método que exporta los datos a Excel + var stream = new XLSXExportBase(); + // Convertimos los resultados de la búsqueda a un formato adecuado para el exportador + var customersData = searchResult.Items.Select(c => new + { + c.Id, + c.Name, + c.Email, + c.Phone, + c.DefaultCommissionPercent, + c.Active + }).ToList(); + // Genera el archivo Excel + var excelFile = stream.ExportExcel(customersData); + // Devuelve el archivo Excel como un array de bytes + return excelFile; + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + throw new Exception($"{ex.Message}", ex); + } + } + #endregion + } +} diff --git a/Core/Services/QuoteService.cs b/Core/Services/QuoteService.cs index 2c02044..94fdc68 100644 --- a/Core/Services/QuoteService.cs +++ b/Core/Services/QuoteService.cs @@ -62,9 +62,9 @@ namespace PhronCare.Core.Services.Sales var newQuote = await _quoteHeaderRepository.AddAsync(quote); // Crear los detalles asociados - if (quote.Details != null) + if (quote.PhSQuoteDetails != null) { - foreach (var detail in quote.Details) + foreach (var detail in quote.PhSQuoteDetails) { detail.QuoteheaderId = newQuote.Id; await _quoteDetailRepository.AddAsync(detail); @@ -72,9 +72,9 @@ namespace PhronCare.Core.Services.Sales } // Crear los roles asociados - if (quote.Roles != null) + if (quote.PhSQuoteRoles != null) { - foreach (var role in quote.Roles) + foreach (var role in quote.PhSQuoteRoles) { role.QuoteheaderId = newQuote.Id; await _quoteRoleRepository.AddAsync(role); @@ -130,9 +130,9 @@ namespace PhronCare.Core.Services.Sales 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 + Profesional = q.PhSQuoteRoles.FirstOrDefault(r => r.Entitytype == "PhS_Professionals")?.Entitytype, + Institución = q.PhSQuoteRoles.FirstOrDefault(r => r.Entitytype == "PhS_Institutions")?.Entitytype, + Paciente = q.PhSQuoteRoles.FirstOrDefault(r => r.Entitytype == "PhS_Patients")?.Entitytype }).ToList(); // Generar archivo Excel diff --git a/Domain/Entities/EPeopleGroup.cs b/Domain/Entities/EPeopleGroup.cs new file mode 100644 index 0000000..e86f127 --- /dev/null +++ b/Domain/Entities/EPeopleGroup.cs @@ -0,0 +1,40 @@ +namespace Domain.Entities +{ + public class EPeopleGroup + { + /// + /// Identificador único del grupo de personas + /// + public int Id { get; set; } + + /// + /// Nombre del grupo de vendedores + /// + public string Name { get; set; } = null!; + + /// + /// Descripción adicional del grupo + /// + public string? Description { get; set; } + + /// + /// Unidad de negocio relacionada (opcional) + /// + public int? BusinessunitsId { get; set; } + + /// + /// Estado activo o inactivo del grupo + /// + public bool Active { get; set; } + + /// + /// Fecha de creación del grupo + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de última modificación + /// + public DateTime? ModifiedAt { get; set; } + } +} diff --git a/Domain/Entities/EPerson.cs b/Domain/Entities/EPerson.cs new file mode 100644 index 0000000..5ce6633 --- /dev/null +++ b/Domain/Entities/EPerson.cs @@ -0,0 +1,59 @@ +namespace Domain.Entities +{ + public class EPerson + { + /// + /// Identificador único de la persona (vendedor/agente) + /// + public int Id { get; set; } + + /// + /// Nombre de la persona + /// + public string Name { get; set; } = null!; + + /// + /// Teléfono de contacto de la persona + /// + public string? Phone { get; set; } + + /// + /// Correo electrónico de la persona + /// + public string? Email { get; set; } + + /// + /// Unidad de negocio asignada + /// + public int BusinessunitsId { get; set; } + + /// + /// Grupo comercial opcional + /// + public int? PeoplegroupsId { get; set; } + + /// + /// Porcentaje de comisión por defecto + /// + public decimal? DefaultCommissionPercent { get; set; } + + /// + /// Activo/Inactivo + /// + public bool Active { get; set; } + + /// + /// Fecha de creación + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de última modificación + /// + public DateTime? ModifiedAt { get; set; } + + public virtual EBusinessUnit Businessunits { get; set; } = null!; + + public virtual EPeopleGroup? Peoplegroups { get; set; } + } +} diff --git a/Domain/Entities/EQuoteDetail.cs b/Domain/Entities/EQuoteDetail.cs index 3d874c3..9153e10 100644 --- a/Domain/Entities/EQuoteDetail.cs +++ b/Domain/Entities/EQuoteDetail.cs @@ -1,14 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Domain.Entities +namespace Domain.Entities { - /// - /// Tabla de detalles de presupuestos - /// public class EQuoteDetail { /// @@ -61,6 +52,8 @@ namespace Domain.Entities /// public DateTime? Modifiedat { get; set; } - public virtual EQuoteHeader Quoteheader { get; set; } = null!; + public virtual EProduct Product { get; set; } = null!; + + public virtual EQuoteHeader PhSQuoteheader { get; set; } = null!; } } diff --git a/Domain/Entities/EQuoteHeader.cs b/Domain/Entities/EQuoteHeader.cs index 4ef9c1a..9442860 100644 --- a/Domain/Entities/EQuoteHeader.cs +++ b/Domain/Entities/EQuoteHeader.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Domain.Entities +namespace Domain.Entities { /// /// Tabla de cabeceras de presupuestos @@ -31,6 +25,11 @@ namespace Domain.Entities /// public int BusinessunitId { get; set; } + /// + /// Identificador único del vendedor + /// + public int PeopleId { get; set; } + /// /// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc. /// @@ -86,8 +85,9 @@ namespace Domain.Entities /// public DateTime? Modifiedat { get; set; } - public virtual ICollection Details { get; set; } = new List(); + public virtual ICollection PhSQuoteDetails { get; set; } = new List(); + + public virtual ICollection PhSQuoteRoles { get; set; } = new List(); - public virtual ICollection Roles { get; set; } = new List(); } } diff --git a/Domain/Generics/PeopleSearchParams.cs b/Domain/Generics/PeopleSearchParams.cs new file mode 100644 index 0000000..09e6429 --- /dev/null +++ b/Domain/Generics/PeopleSearchParams.cs @@ -0,0 +1,8 @@ +namespace Domain.Generics +{ + public class PeopleSearchParams: PagedRequest + { + public string? Name { get; set; } + public string? Email { get; set; } + } +} \ No newline at end of file diff --git a/Domain/obj/Domain.csproj.nuget.g.props b/Domain/obj/Domain.csproj.nuget.g.props index 8b4eb7e..3efed38 100644 --- a/Domain/obj/Domain.csproj.nuget.g.props +++ b/Domain/obj/Domain.csproj.nuget.g.props @@ -7,7 +7,7 @@ $(UserProfile)\.nuget\packages\ C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.13.2 + 6.13.1 diff --git a/Models/Interfaces/IPhSPeopleRepository.cs b/Models/Interfaces/IPhSPeopleRepository.cs new file mode 100644 index 0000000..839ee28 --- /dev/null +++ b/Models/Interfaces/IPhSPeopleRepository.cs @@ -0,0 +1,17 @@ +using Domain.Entities; +using Domain.Generics; + +namespace Models.Interfaces +{ + public interface IPhSPeopleRepository + { + Task> GetAllAsync(int page = 1, int pageSize = 50); + Task> SearchAsync(string? name, string? email, + int page = 1, int pageSize = 50); + Task GetByIdAsync(int id); + Task AddAsync(EPerson person); + Task UpdateAsync(EPerson person); + Task DeleteAsync(int id); + + } +} diff --git a/Models/Models/PhSBusinessUnit.cs b/Models/Models/PhSBusinessUnit.cs index bd75a63..4f58fc1 100644 --- a/Models/Models/PhSBusinessUnit.cs +++ b/Models/Models/PhSBusinessUnit.cs @@ -13,5 +13,7 @@ public partial class PhSBusinessUnit public string? Manager { get; set; } + public virtual ICollection PhSPeople { get; set; } = new List(); + public virtual ICollection PhSProducts { get; set; } = new List(); } diff --git a/Models/Models/PhSPeopleGroup.cs b/Models/Models/PhSPeopleGroup.cs new file mode 100644 index 0000000..6a18db0 --- /dev/null +++ b/Models/Models/PhSPeopleGroup.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Models.Models; + +public partial class PhSPeopleGroup +{ + /// + /// Identificador único del grupo de personas + /// + public int Id { get; set; } + + /// + /// Nombre del grupo de vendedores + /// + public string Name { get; set; } = null!; + + /// + /// Descripción adicional del grupo + /// + public string? Description { get; set; } + + /// + /// Unidad de negocio relacionada (opcional) + /// + public int? BusinessunitsId { get; set; } + + /// + /// Estado activo o inactivo del grupo + /// + public bool Active { get; set; } + + /// + /// Fecha de creación del grupo + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de última modificación + /// + public DateTime? ModifiedAt { get; set; } + + public virtual ICollection PhSPeople { get; set; } = new List(); +} diff --git a/Models/Models/PhSPerson.cs b/Models/Models/PhSPerson.cs new file mode 100644 index 0000000..635d8ba --- /dev/null +++ b/Models/Models/PhSPerson.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Models.Models; + +public partial class PhSPerson +{ + /// + /// Identificador único de la persona (vendedor/agente) + /// + public int Id { get; set; } + + /// + /// Nombre de la persona + /// + public string Name { get; set; } = null!; + + /// + /// Teléfono de contacto de la persona + /// + public string? Phone { get; set; } + + /// + /// Correo electrónico de la persona + /// + public string? Email { get; set; } + + /// + /// Unidad de negocio asignada + /// + public int BusinessunitsId { get; set; } + + /// + /// Grupo comercial opcional + /// + public int? PeoplegroupsId { get; set; } + + /// + /// Porcentaje de comisión por defecto + /// + public decimal? DefaultCommissionPercent { get; set; } + + /// + /// Activo/Inactivo + /// + public bool Active { get; set; } + + /// + /// Fecha de creación + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de última modificación + /// + public DateTime? ModifiedAt { get; set; } + + public virtual PhSBusinessUnit Businessunits { get; set; } = null!; + + public virtual PhSPeopleGroup? Peoplegroups { get; set; } +} diff --git a/Models/Models/PhSProduct.cs b/Models/Models/PhSProduct.cs index 55c07ff..68dd941 100644 --- a/Models/Models/PhSProduct.cs +++ b/Models/Models/PhSProduct.cs @@ -26,4 +26,6 @@ public partial class PhSProduct public virtual PhSBusinessUnit? Businessunits { get; set; } public virtual PhSProductCategory? Category { get; set; } + + public virtual ICollection PhSQuoteDetails { get; set; } = new List(); } diff --git a/Models/Models/PhSQuoteDetail.cs b/Models/Models/PhSQuoteDetail.cs index 43d2f3d..ffab823 100644 --- a/Models/Models/PhSQuoteDetail.cs +++ b/Models/Models/PhSQuoteDetail.cs @@ -58,5 +58,7 @@ public partial class PhSQuoteDetail /// public DateTime? Modifiedat { get; set; } + public virtual PhSProduct Product { get; set; } = null!; + public virtual PhSQuoteHeader Quoteheader { get; set; } = null!; } diff --git a/Models/Models/PhSQuoteHeader.cs b/Models/Models/PhSQuoteHeader.cs index 1676202..3967735 100644 --- a/Models/Models/PhSQuoteHeader.cs +++ b/Models/Models/PhSQuoteHeader.cs @@ -28,6 +28,11 @@ public partial class PhSQuoteHeader /// public int BusinessunitId { get; set; } + /// + /// Identificador único del vendedor + /// + public int PeopleId { get; set; } + /// /// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc. /// diff --git a/Models/Models/PhronCareOperationsHubContext.cs b/Models/Models/PhronCareOperationsHubContext.cs index 7ab03db..8ba2c76 100644 --- a/Models/Models/PhronCareOperationsHubContext.cs +++ b/Models/Models/PhronCareOperationsHubContext.cs @@ -43,6 +43,10 @@ public partial class PhronCareOperationsHubContext : DbContext public virtual DbSet PhSPatients { get; set; } + public virtual DbSet PhSPeopleGroups { get; set; } + + public virtual DbSet PhSPeople { get; set; } + public virtual DbSet PhSProducts { get; set; } public virtual DbSet PhSProductCategories { get; set; } @@ -58,15 +62,8 @@ public partial class PhronCareOperationsHubContext : DbContext public virtual DbSet PhSQuoteRoles { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - #region VERSION DOCKER - { - if (!optionsBuilder.IsConfigured) - { - // Dejarlo vacío para usar la configuración externa desde Program.cs o Startup.cs - } - } - #endregion - //=> optionsBuilder.UseSqlServer("data source=srv01.saludlab.com.ar,39458;initial catalog=phronCare_OperationsHub;User ID=sa;Password=HS|s[~xxQzTo/n>9jO;encrypt=False;trustServerCertificate=True;MultipleActiveResultSets=True"); +#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. + => optionsBuilder.UseSqlServer("data source=srv01.saludlab.com.ar,39458;initial catalog=phronCare_OperationsHub;User ID=sa;Password=HS|s[~xxQzTo/n>9jO;encrypt=False;trustServerCertificate=True;MultipleActiveResultSets=True"); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -481,6 +478,96 @@ public partial class PhronCareOperationsHubContext : DbContext .HasConstraintName("FK_Patients_DocumentTypes"); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PK__PhS_Peop__3213E83F8005BEA1"); + + entity.ToTable("PhS_PeopleGroups"); + + entity.Property(e => e.Id) + .HasComment("Identificador único del grupo de personas") + .HasColumnName("id"); + entity.Property(e => e.Active) + .HasDefaultValue(true) + .HasComment("Estado activo o inactivo del grupo") + .HasColumnName("active"); + entity.Property(e => e.BusinessunitsId) + .HasComment("Unidad de negocio relacionada (opcional)") + .HasColumnName("businessunits_id"); + entity.Property(e => e.CreatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("Fecha de creación del grupo") + .HasColumnType("datetime") + .HasColumnName("created_at"); + entity.Property(e => e.Description) + .HasMaxLength(255) + .HasComment("Descripción adicional del grupo") + .HasColumnName("description"); + entity.Property(e => e.ModifiedAt) + .HasComment("Fecha de última modificación") + .HasColumnType("datetime") + .HasColumnName("modified_at"); + entity.Property(e => e.Name) + .HasMaxLength(100) + .HasComment("Nombre del grupo de vendedores") + .HasColumnName("name"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PK__PhS_Peop__3213E83F52F7C072"); + + entity.ToTable("PhS_People"); + + entity.Property(e => e.Id) + .HasComment("Identificador único de la persona (vendedor/agente)") + .HasColumnName("id"); + entity.Property(e => e.Active) + .HasDefaultValue(true) + .HasComment("Activo/Inactivo") + .HasColumnName("active"); + entity.Property(e => e.BusinessunitsId) + .HasComment("Unidad de negocio asignada") + .HasColumnName("businessunits_id"); + entity.Property(e => e.CreatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("Fecha de creación") + .HasColumnType("datetime") + .HasColumnName("created_at"); + entity.Property(e => e.DefaultCommissionPercent) + .HasComment("Porcentaje de comisión por defecto") + .HasColumnType("decimal(5, 2)") + .HasColumnName("default_commission_percent"); + entity.Property(e => e.Email) + .HasMaxLength(100) + .HasComment("Correo electrónico de la persona") + .HasColumnName("email"); + entity.Property(e => e.ModifiedAt) + .HasComment("Fecha de última modificación") + .HasColumnType("datetime") + .HasColumnName("modified_at"); + entity.Property(e => e.Name) + .HasMaxLength(100) + .HasComment("Nombre de la persona") + .HasColumnName("name"); + entity.Property(e => e.PeoplegroupsId) + .HasComment("Grupo comercial opcional") + .HasColumnName("peoplegroups_id"); + entity.Property(e => e.Phone) + .HasMaxLength(50) + .HasComment("Teléfono de contacto de la persona") + .HasColumnName("phone"); + + entity.HasOne(d => d.Businessunits).WithMany(p => p.PhSPeople) + .HasForeignKey(d => d.BusinessunitsId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_people_businessunits"); + + entity.HasOne(d => d.Peoplegroups).WithMany(p => p.PhSPeople) + .HasForeignKey(d => d.PeoplegroupsId) + .HasConstraintName("FK_people_peoplegroups"); + }); + modelBuilder.Entity(entity => { entity.ToTable("PhS_Products"); @@ -651,6 +738,11 @@ public partial class PhronCareOperationsHubContext : DbContext .HasColumnType("decimal(18, 2)") .HasColumnName("unitprice"); + entity.HasOne(d => d.Product).WithMany(p => p.PhSQuoteDetails) + .HasForeignKey(d => d.ProductId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_PhS_QuoteDetails_PhS_Products"); + entity.HasOne(d => d.Quoteheader).WithMany(p => p.PhSQuoteDetails) .HasForeignKey(d => d.QuoteheaderId) .OnDelete(DeleteBehavior.ClientSetNull) @@ -704,6 +796,10 @@ public partial class PhronCareOperationsHubContext : DbContext entity.Property(e => e.Observations) .HasComment("Observaciones internas") .HasColumnName("observations"); + entity.Property(e => e.PeopleId) + .HasDefaultValue(1) + .HasComment("Identificador único del vendedor") + .HasColumnName("people_id"); entity.Property(e => e.Printcount) .HasComment("Cantidad de impresiones") .HasColumnName("printcount"); diff --git a/Models/Repositories/PhSCustomerRepository.cs b/Models/Repositories/PhSCustomerRepository.cs index 0a1ba78..a6cf9ef 100644 --- a/Models/Repositories/PhSCustomerRepository.cs +++ b/Models/Repositories/PhSCustomerRepository.cs @@ -178,4 +178,4 @@ namespace Models.Repositories } #endregion } -} +} \ No newline at end of file diff --git a/Models/Repositories/PhSPeopleRepository.cs b/Models/Repositories/PhSPeopleRepository.cs new file mode 100644 index 0000000..2b97676 --- /dev/null +++ b/Models/Repositories/PhSPeopleRepository.cs @@ -0,0 +1,92 @@ +using Azure; +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 PhSPeopleRepository(PhronCareOperationsHubContext context) : IPhSPeopleRepository + { + #region Declaraciones + private readonly PhronCareOperationsHubContext _context = context; + #endregion + #region Métodos + public async Task> GetAllAsync(int page = 1, int pageSize = 50) + { + var query = _context.PhSPeople + .Include(p => p.Businessunits) + .Include(p => p.Peoplegroups) + .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> SearchAsync(string? name, string? email, + int page = 1, int pageSize = 50) + { + var query = _context.PhSPeople.AsQueryable(); + + if (!string.IsNullOrEmpty(name)) + { + query = query.Where(p => (p.Name).Contains(name)); + } + + if (!string.IsNullOrEmpty(email)) + { + query = query.Where(p => p.Email != null && p.Email.Contains(email)); + } + + 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.PhSPeople + .Include(p => p.Businessunits) + .Include(p => p.Peoplegroups) + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == id); + + return entity != null ? EntityMapper.MapEntity(entity) : null; + } + public async Task AddAsync(EPerson person) + { + var dbEntity = EntityMapper.MapEntity(person); + _context.PhSPeople.Add(dbEntity); + await _context.SaveChangesAsync(); + return EntityMapper.MapEntity(dbEntity); + } + public async Task UpdateAsync(EPerson person) + { + var dbEntity = EntityMapper.MapEntity(person); + _context.PhSPeople.Update(dbEntity); + await _context.SaveChangesAsync(); + } + public async Task DeleteAsync(int id) + { + var entity = await _context.PhSPeople.FindAsync(id); + if (entity != null) + { + _context.PhSPeople.Remove(entity); + await _context.SaveChangesAsync(); + } + } + #endregion + } +} diff --git a/Models/obj/Models.csproj.nuget.g.props b/Models/obj/Models.csproj.nuget.g.props index 57bd42c..d21a6b0 100644 --- a/Models/obj/Models.csproj.nuget.g.props +++ b/Models/obj/Models.csproj.nuget.g.props @@ -7,7 +7,7 @@ $(UserProfile)\.nuget\packages\ C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.13.2 + 6.13.1 diff --git a/phronCare.API/Controllers/Sales/PeopleController.cs b/phronCare.API/Controllers/Sales/PeopleController.cs new file mode 100644 index 0000000..b470c39 --- /dev/null +++ b/phronCare.API/Controllers/Sales/PeopleController.cs @@ -0,0 +1,144 @@ +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 PeopleController : ControllerBase + { + private readonly IPeopleDom _peopleService; + + public PeopleController(IPeopleDom peopleService) + { + _peopleService = peopleService ?? throw new ArgumentNullException(nameof(peopleService)); + } + + #region Get Methods + + [HttpGet("all")] + public async Task GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50) + { + try + { + var result = await _peopleService.GetAllAsync(page, pageSize); + return Ok(result); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + [HttpGet("{id:int}")] + public async Task GetById(int id) + { + try + { + var person = await _peopleService.GetByIdAsync(id); + return person == null ? NotFound() : Ok(person); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + [HttpGet("search")] + public async Task SearchAsync([FromQuery] string? name, [FromQuery] string? email, [FromQuery] int page = 1, [FromQuery] int pageSize = 10) + { + try + { + var result = await _peopleService.SearchAsync(name, email, page, pageSize); + return Ok(result); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + #endregion + + #region Post/Put/Delete Methods + + [HttpPost("create")] + public async Task Create([FromBody] EPerson person) + { + try + { + if (person == null) + return BadRequest("La persona no puede ser nula."); + + var result = await _peopleService.CreateAsync(person); + return Ok(result); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + [HttpPut("update")] + public async Task Update([FromBody] EPerson person) + { + try + { + if (person == null || person.Id <= 0) + return BadRequest("La persona es inválida o no tiene un ID válido."); + + var success = await _peopleService.UpdateAsync(person); + return success ? Ok("Persona actualizada correctamente.") : NotFound($"No se encontró la persona con ID {person.Id}."); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + [HttpDelete("delete/{id:int}")] + public async Task Delete(int id) + { + try + { + var success = await _peopleService.DeleteAsync(id); + return success ? Ok("Persona eliminada correctamente.") : NotFound($"No se encontró la persona con ID {id}."); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + [HttpPost("exportfiltered")] + public async Task ExportFilteredPeople([FromBody] PeopleSearchParams searchParams) + { + try + { + var file = await _peopleService.ExportFilteredCustomersToExcelAsync(searchParams); + return File(file, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Vendedores.xlsx"); + } + catch (Exception ex) + { + return InternalError(ex); + } + } + + #endregion + + #region Helpers + + private IActionResult InternalError(Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + + #endregion + } +} diff --git a/phronCare.API/Program.cs b/phronCare.API/Program.cs index 862c086..b6e8a09 100644 --- a/phronCare.API/Program.cs +++ b/phronCare.API/Program.cs @@ -72,7 +72,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json index b734fe5..039d71d 100644 --- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json +++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json @@ -828,6 +828,138 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.PeopleController", + "Method": "GetById", + "RelativePath": "api/People/{id}", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "id", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.PeopleController", + "Method": "GetAll", + "RelativePath": "api/People/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.PeopleController", + "Method": "Create", + "RelativePath": "api/People/create", + "HttpMethod": "POST", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "person", + "Type": "Domain.Entities.EPerson", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.PeopleController", + "Method": "Delete", + "RelativePath": "api/People/delete/{id}", + "HttpMethod": "DELETE", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "id", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.PeopleController", + "Method": "ExportFilteredPeople", + "RelativePath": "api/People/exportfiltered", + "HttpMethod": "POST", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "searchParams", + "Type": "Domain.Generics.PeopleSearchParams", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.PeopleController", + "Method": "SearchAsync", + "RelativePath": "api/People/search", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "name", + "Type": "System.String", + "IsRequired": false + }, + { + "Name": "email", + "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.PeopleController", + "Method": "Update", + "RelativePath": "api/People/update", + "HttpMethod": "PUT", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "person", + "Type": "Domain.Entities.EPerson", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.Sales.ProductController", "Method": "GetById", diff --git a/phronCare.UIBlazor/Pages/Sales/People.razor b/phronCare.UIBlazor/Pages/Sales/People.razor new file mode 100644 index 0000000..a43ea63 --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/People.razor @@ -0,0 +1,208 @@ +@page "/sales/people" +@using Domain.Entities +@using Domain.Generics +@using phronCare.UIBlazor.Services.Sales +@inject IToastService toastService +@inject NavigationManager Navigation +@inject PeopleService peopleService + +
+
+

Búsqueda de Vendedores

+
+ +
+
+ + + + + + + +
+ +
+ +
+ @if (TablaPersonas != null && TablaPersonas.Any()) + { + + } + else + { +

No hay resultados.

+ } +
+
+ + +
+ +@code { + protected override void OnInitialized() + { + botones = new List + { + new PhTable.ButtonOptions + { + Caption = "Editar", + ElementClass = "btn btn-dark btn-sm rounded-pill", + UrlAction = "/sales/personform/", + OnClickAction = async (id) => + { + if (int.TryParse(id, out var personId)) + { + Navigation.NavigateTo($"/sales/personform/{personId}"); + } + } + } + }; + } + + private PeopleSearchParams SearchParams = new(); + private PagedResult? PagedResult; + private List> TablaPersonas = new(); + private List TableColumns = new() + { + "Id", "Nombre", "Email", "Teléfono", "Comision", "Activo" + }; + private int PaginaDeseada = 1; + + private async Task BuscarPersonas() + { + await CargarPersonas(); + } + + private async Task CargarPersonas() + { + PagedResult = await peopleService.SearchPeopleAsync(SearchParams); + + if (PagedResult?.Items is not null) + { + TablaPersonas = PagedResult.Items.Select(p => new Dictionary + { + { "Id", p.Id }, + { "Nombre", $"{p.Name}" }, + { "Email", p.Email ?? string.Empty }, + { "Teléfono", p.Phone ?? string.Empty }, + { "Comision", p.DefaultCommissionPercent ?? 0 }, + { "Activo", p.Active ? "Sí" : "No" } + }).ToList(); + } + } + + private async Task ExportarExcel() + { + var searchParams = new PeopleSearchParams + { + Name = SearchParams.Name, // Aquí podés obtener los filtros de los campos en el formulario + Email = SearchParams.Email, + Page = 1, + PageSize = int.MaxValue // Puedes ajustar el tamaño de la página para exportar todos los registros + }; + try + { + await peopleService.ExportFilteredAsync(searchParams); + toastService.ShowSuccess("Exportación completada exitosamente."); + } + catch (Exception ex) + { + toastService.ShowError($"{ex.Message}"); + } + } + + private async Task PrimeraPagina() + { + SearchParams.Page = 1; + await BuscarPersonas(); + } + + private async Task UltimaPagina() + { + SearchParams.Page = TotalPaginas; + await BuscarPersonas(); + } + + private async Task IrAPagina() + { + if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas) + { + SearchParams.Page = PaginaDeseada; + await BuscarPersonas(); + } + else + { + toastService.ShowWarning("Número de página fuera de rango."); + } + } + + private async Task SiguientePagina() => await CambiarPagina(1); + private async Task AnteriorPagina() => await CambiarPagina(-1); + + private async Task CambiarPagina(int delta) + { + var nuevaPagina = SearchParams.Page + delta; + if (nuevaPagina >= 1 && nuevaPagina <= TotalPaginas) + { + SearchParams.Page = nuevaPagina; + await CargarPersonas(); + } + } + + private void NuevaPersona() + { + Navigation.NavigateTo("/sales/personform/"); + } + + private void Cancelar() + { + Navigation.NavigateTo("/DashboardPanel"); + } + + private bool PuedeRetroceder => PagedResult != null && SearchParams.Page > 1; + private bool PuedeAvanzar => PagedResult != null && SearchParams.Page < TotalPaginas; + private int TotalPaginas => PagedResult is null ? 1 : + (int)Math.Ceiling((double)(PagedResult.TotalItems) / SearchParams.PageSize); + + List botones = new(); +} diff --git a/phronCare.UIBlazor/Pages/Sales/PersonForm.razor b/phronCare.UIBlazor/Pages/Sales/PersonForm.razor new file mode 100644 index 0000000..b98ad21 --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/PersonForm.razor @@ -0,0 +1,167 @@ +@page "/sales/personform" +@page "/sales/personform/{PersonId:int}" + +@using System.ComponentModel.DataAnnotations +@using phronCare.UIBlazor.Pages.Shared.Modals +@using phronCare.UIBlazor.Services.Sales + +@inject IModalService Modal +@inject IToastService toastService +@inject PeopleService peopleService +@inject NavigationManager Navigation +@inject BusinessUnitService businessUnitService + +
+
+

Información de la Persona

+
+ +
+ + + + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + + + @foreach (var unit in businessUnits) + { + + } + +
+
+ + + + @foreach (var group in peopleGroups) + { + + } + +
+
+ +
+
+
+ + +
+
+
+
+
+ + +
+ +@code { + [Parameter] + public int? PersonId { get; set; } + + private EPerson person { get; set; } = new(); + private bool isSaving = false; + private string returnUrl = "/sales/people"; + + private List businessUnits = new(); + private List peopleGroups = new(); + + protected override async Task OnInitializedAsync() + { + await LoadBusinessUnits(); + await LoadPeopleGroups(); + + if (PersonId.HasValue) + { + person = await peopleService.GetPersonByIdAsync(PersonId.Value) ?? new(); + } + } + + private async Task HandleValidSubmit() + { + var parameters = new ModalParameters(); + parameters.Add("Message", "¿Desea guardar los cambios de la persona?"); + var modal = Modal.Show("Confirmación", parameters); + var result = await modal.Result; + + if (result.Cancelled) + return; + + try + { + isSaving = true; + + if (PersonId.HasValue) + { + await peopleService.UpdatePersonAsync(person); + } + else + { + await peopleService.CreatePersonAsync(person); + } + + toastService.ShowSuccess("Persona guardada exitosamente."); + Navigation.NavigateTo(returnUrl); + } + catch (Exception ex) + { + toastService.ShowError($"Error: {ex.Message}"); + } + finally + { + isSaving = false; + } + } + + private void Cancel() + { + Navigation.NavigateTo(returnUrl); + } + + private async Task LoadBusinessUnits() + { + businessUnits = await businessUnitService.GetAllAsync(); + } + + private async Task LoadPeopleGroups() + { + // Simulamos carga de Grupos Comerciales. Debería venir de un servicio real. + peopleGroups = new List + { + new EPeopleGroup { Id = 1, Name = "Grupo 1" }, + new EPeopleGroup { Id = 2, Name = "Grupo 2" }, + }; + } +} diff --git a/phronCare.UIBlazor/Program.cs b/phronCare.UIBlazor/Program.cs index 73fa9a8..5c483ba 100644 --- a/phronCare.UIBlazor/Program.cs +++ b/phronCare.UIBlazor/Program.cs @@ -7,7 +7,6 @@ using phronCare.UIBlazor.Services.UI; using Blazored.Modal; using Blazored.Toast; using phronCare.UIBlazor.Services.Tickets; -using phronCare.UIBlazor.Shared; using phronCare.UIBlazor.Services.Sales; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -48,6 +47,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); #endregion diff --git a/phronCare.UIBlazor/Services/Sales/PeopleGroupService.cs b/phronCare.UIBlazor/Services/Sales/PeopleGroupService.cs new file mode 100644 index 0000000..cd5f4ba --- /dev/null +++ b/phronCare.UIBlazor/Services/Sales/PeopleGroupService.cs @@ -0,0 +1,21 @@ +using System.Net.Http.Json; +using Domain.Entities; + +namespace phronCare.UIBlazor.Services.People +{ + public class PeopleGroupService + { + private readonly HttpClient _http; + + public PeopleGroupService(HttpClient http) + { + _http = http; + } + + public async Task> GetAllAsync() + { + var result = await _http.GetFromJsonAsync>("/api/PeopleGroup/All"); + return result ?? new List(); + } + } +} diff --git a/phronCare.UIBlazor/Services/Sales/PeopleService.cs b/phronCare.UIBlazor/Services/Sales/PeopleService.cs new file mode 100644 index 0000000..b63c976 --- /dev/null +++ b/phronCare.UIBlazor/Services/Sales/PeopleService.cs @@ -0,0 +1,94 @@ +using Domain.Entities; +using Domain.Generics; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text; +using Microsoft.JSInterop; +using System.Reflection; + +namespace phronCare.UIBlazor.Services.Sales +{ + public class PeopleService + { + private readonly HttpClient _http; + private readonly IJSRuntime _js; + + public PeopleService(HttpClient http, IJSRuntime js) + { + _http = http; + _js = js; + } + + // Buscar personas + public async Task?> SearchPeopleAsync(PeopleSearchParams searchParams) + { + var url = $"api/People/search?" + + $"name={searchParams.Name}&" + + $"email={searchParams.Email}&" + + $"page={searchParams.Page}&" + + $"pageSize={searchParams.PageSize}"; + return await _http.GetFromJsonAsync>(url); + } + + public async Task ExportFilteredAsync(PeopleSearchParams searchParams) + { + try + { + var content = new StringContent(JsonSerializer.Serialize(searchParams), Encoding.UTF8, "application/json"); + var response = await _http.PostAsync("api/People/exportfiltered", content); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception(errorContent); + } + + var bytes = await response.Content.ReadAsByteArrayAsync(); + var base64 = Convert.ToBase64String(bytes); + var timestamp = DateTime.Now.ToString("yyyyMMddHHmm"); + var fileName = $"{timestamp}_vendedores.xlsx"; + await _js.InvokeVoidAsync("saveAsFile", fileName, base64); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + var message = ex.Message ?? "No message provided"; + throw new Exception($"{message}", ex); + } + } + + // Obtener una persona por Id (para formulario) + public async Task GetPersonByIdAsync(int id) + { + return await _http.GetFromJsonAsync($"api/People/{id}"); + } + + // Crear nueva persona + public async Task CreatePersonAsync(EPerson person) + { + var content = new StringContent(JsonSerializer.Serialize(person), Encoding.UTF8, "application/json"); + var response = await _http.PostAsync("api/People/create", content); // CAMBIADO + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception(errorContent); + } + } + + + // Actualizar persona existente + public async Task UpdatePersonAsync(EPerson person) + { + var content = new StringContent(JsonSerializer.Serialize(person), Encoding.UTF8, "application/json"); + var response = await _http.PutAsync("api/People/update", content); // CAMBIADO + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new Exception(errorContent); + } + } + + } +} diff --git a/phronCare.UIBlazor/Shared/NavMenu.razor b/phronCare.UIBlazor/Shared/NavMenu.razor index 4e723a6..58790c8 100644 --- a/phronCare.UIBlazor/Shared/NavMenu.razor +++ b/phronCare.UIBlazor/Shared/NavMenu.razor @@ -86,6 +86,11 @@ Profesionales +