Add Professional y Specialty on API UI
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m31s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m31s
This commit is contained in:
parent
319f7234c5
commit
f4d6bd9e28
17
Core/Interfaces/IProfessionalDom.cs
Normal file
17
Core/Interfaces/IProfessionalDom.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
|
||||
namespace Core.Interfaces
|
||||
{
|
||||
public interface IProfessionalDom
|
||||
{
|
||||
Task<EProfessional> CreateAsync(EProfessional entity);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<byte[]> ExportFilteredProfessionalsToExcelAsync(ProfessionalSearchParams searchParams);
|
||||
Task<PagedResult<EProfessional>> GetAllAsync(int page = 1, int pageSize = 50);
|
||||
Task<EProfessional?> GetByIdAsync(int id);
|
||||
Task<PagedResult<EProfessional>> SearchAsync(string? fullname, string? document, string? type,
|
||||
int page = 1, int pageSize = 50);
|
||||
Task<bool> UpdateAsync(EProfessional entity);
|
||||
}
|
||||
}
|
||||
10
Core/Interfaces/IProfessionalSpecialtyDom.cs
Normal file
10
Core/Interfaces/IProfessionalSpecialtyDom.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Domain.Entities;
|
||||
|
||||
namespace Core.Interfaces
|
||||
{
|
||||
public interface IProfessionalSpecialtyDom
|
||||
{
|
||||
Task<IEnumerable<EProfessionalSpecialty>> GetAllAsync();
|
||||
Task<EProfessionalSpecialty?> GetByNameAsync(string name);
|
||||
}
|
||||
}
|
||||
145
Core/Services/ProfessionalService.cs
Normal file
145
Core/Services/ProfessionalService.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using Core.Interfaces;
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using Models.Interfaces;
|
||||
using System.Reflection;
|
||||
using Transversal.Services;
|
||||
|
||||
namespace Core.Services
|
||||
{
|
||||
public class ProfessionalService : IProfessionalDom
|
||||
{
|
||||
#region Declaraciones y Constructor
|
||||
private readonly IPhSProfessionalRepository _repository;
|
||||
|
||||
public ProfessionalService(IPhSProfessionalRepository professionalRepository)
|
||||
{
|
||||
_repository = professionalRepository ?? throw new ArgumentNullException(nameof(professionalRepository));
|
||||
}
|
||||
#endregion
|
||||
#region Métodos
|
||||
public async Task<PagedResult<EProfessional>> GetAllAsync(int page = 1, int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetAllAsync(page, pageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<EProfessional?> GetByIdAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<EProfessional> CreateAsync(EProfessional entity)
|
||||
{
|
||||
if (entity is null)
|
||||
throw new ArgumentNullException(nameof(entity), "El profesional no puede ser nulo.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entity.Fullname))
|
||||
throw new ArgumentException("Debe ingresar el nombre completo del profesional.", nameof(entity.Fullname));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entity.DocumentNumber))
|
||||
throw new ArgumentException("Debe ingresar un número de documento.", nameof(entity.DocumentNumber));
|
||||
|
||||
return await _repository.CreateAsync(entity);
|
||||
}
|
||||
public async Task<bool> UpdateAsync(EProfessional entity)
|
||||
{
|
||||
if (entity is null)
|
||||
throw new ArgumentNullException(nameof(entity), "El profesional no puede ser nulo.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entity.Fullname))
|
||||
throw new ArgumentException("Debe ingresar el nombre completo del profesional.", nameof(entity.Fullname));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entity.DocumentNumber))
|
||||
throw new ArgumentException("Debe ingresar un número de documento o matricula.", nameof(entity.DocumentNumber));
|
||||
|
||||
return await _repository.UpdateAsync(entity);
|
||||
}
|
||||
public async Task<PagedResult<EProfessional>> SearchAsync(
|
||||
string? fullname, string? document, string? type,
|
||||
int page = 1, int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.SearchAsync(fullname, document, type, page, pageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<byte[]> ExportFilteredProfessionalsToExcelAsync(ProfessionalSearchParams searchParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Realiza la búsqueda de clientes con los parámetros proporcionados
|
||||
var searchResult = await SearchAsync(
|
||||
searchParams.Fullname,
|
||||
searchParams.Document,
|
||||
searchParams.Type,
|
||||
searchParams.Page,
|
||||
searchParams.PageSize
|
||||
);
|
||||
|
||||
if (searchResult?.Items is null || !searchResult.Items.Any())
|
||||
throw new Exception("No se encontraron profesionales para exportar.");
|
||||
|
||||
var stream = new XLSXExportBase();
|
||||
var professionalsData = searchResult.Items.Select(p => new
|
||||
{
|
||||
p.Id,
|
||||
p.Fullname,
|
||||
p.DocumenttypeName,
|
||||
p.DocumentNumber,
|
||||
Tipo = p.Type,
|
||||
Especialidad = p.Specialty?.Name,
|
||||
p.Email,
|
||||
Teléfono1 = p.Phone1,
|
||||
Teléfono2 = p.Phone2,
|
||||
Dirección = p.Address,
|
||||
Ciudad = p.City,
|
||||
Provincia = p.Province,
|
||||
CódigoPostal = p.Postalcode,
|
||||
Matrícula = p.License,
|
||||
p.Active,
|
||||
FechaAlta = p.Createdat.ToString("dd/MM/yyyy")
|
||||
}).ToList();
|
||||
|
||||
|
||||
return stream.ExportExcel(professionalsData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.DeleteAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
44
Core/Services/ProfessionalSpecialtyService.cs
Normal file
44
Core/Services/ProfessionalSpecialtyService.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Core.Interfaces;
|
||||
using Domain.Entities;
|
||||
using Models.Interfaces;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Core.Services
|
||||
{
|
||||
public class ProfessionalSpecialtyService : IProfessionalSpecialtyDom
|
||||
{
|
||||
#region Declaraciones y Constructor
|
||||
private readonly IPhSProfessionalSpecialtyRepository _repository;
|
||||
public ProfessionalSpecialtyService(IPhSProfessionalSpecialtyRepository repository)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
}
|
||||
#endregion
|
||||
#region Metodos de clase
|
||||
public async Task<IEnumerable<EProfessionalSpecialty>> GetAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{methodName} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<EProfessionalSpecialty?> GetByNameAsync(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetByNameAsync(name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{methodName} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
30
Domain/Entities/EProfessional.cs
Normal file
30
Domain/Entities/EProfessional.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Domain.Entities
|
||||
{
|
||||
public class EProfessional
|
||||
{
|
||||
public int Id { get; set; }
|
||||
[Required(ErrorMessage = "Debe ingresar el nombre del profesional.")]
|
||||
public string Fullname { get; set; } = null!;
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
[Required(ErrorMessage = "Debe seleccionar una provincia.")]
|
||||
public string? Province { get; set; }
|
||||
public string? Postalcode { get; set; }
|
||||
public string? Phone1 { get; set; }
|
||||
public string? Phone2 { get; set; }
|
||||
public string? License { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? DocumenttypeName { get; set; }
|
||||
public string? DocumentNumber { get; set; }
|
||||
[Required(ErrorMessage = "Debe seleccionar un tipo de documento.")]
|
||||
public string? Type { get; set; }
|
||||
[Required(ErrorMessage = "Debe seleccionar la especialidad del profesional.")]
|
||||
public int? SpecialtyId { get; set; }
|
||||
public bool Active { get; set; } = true;
|
||||
public DateTime Createdat { get; set; }
|
||||
public virtual EProfessionalSpecialty? Specialty { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
9
Domain/Entities/EProfessionalSpecialty.cs
Normal file
9
Domain/Entities/EProfessionalSpecialty.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Domain.Entities
|
||||
{
|
||||
public class EProfessionalSpecialty
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
}
|
||||
9
Domain/Generics/ProfessionalSearchParams.cs
Normal file
9
Domain/Generics/ProfessionalSearchParams.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Domain.Generics
|
||||
{
|
||||
public class ProfessionalSearchParams : PagedRequest
|
||||
{
|
||||
public string? Fullname { get; set; }
|
||||
public string? Document{ get; set; }
|
||||
public string? Type { get; set; }
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
|
||||
|
||||
17
Models/Interfaces/IPhSProfessionalRepository.cs
Normal file
17
Models/Interfaces/IPhSProfessionalRepository.cs
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
|
||||
namespace Models.Interfaces
|
||||
{
|
||||
public interface IPhSProfessionalRepository
|
||||
{
|
||||
Task<EProfessional> CreateAsync(EProfessional entity);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<PagedResult<EProfessional>> GetAllAsync(int page = 1, int pageSize = 50);
|
||||
Task<EProfessional?> GetByIdAsync(int id);
|
||||
Task<PagedResult<EProfessional>> SearchAsync(string? fullname, string? document, string? type,
|
||||
int page = 1, int pageSize = 50);
|
||||
Task<bool> UpdateAsync(EProfessional entity);
|
||||
}
|
||||
}
|
||||
10
Models/Interfaces/IPhSProfessionalSpecialtyRepository.cs
Normal file
10
Models/Interfaces/IPhSProfessionalSpecialtyRepository.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Domain.Entities;
|
||||
|
||||
namespace Models.Interfaces
|
||||
{
|
||||
public interface IPhSProfessionalSpecialtyRepository
|
||||
{
|
||||
Task<IEnumerable<EProfessionalSpecialty>> GetAllAsync();
|
||||
Task<EProfessionalSpecialty?> GetByNameAsync(string name);
|
||||
}
|
||||
}
|
||||
41
Models/Models/PhSProfessional.cs
Normal file
41
Models/Models/PhSProfessional.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhSProfessional
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Fullname { get; set; } = null!;
|
||||
|
||||
public string? Address { get; set; }
|
||||
|
||||
public string? City { get; set; }
|
||||
|
||||
public string? Province { get; set; }
|
||||
|
||||
public string? Postalcode { get; set; }
|
||||
|
||||
public string? Phone1 { get; set; }
|
||||
|
||||
public string? Phone2 { get; set; }
|
||||
|
||||
public string? License { get; set; }
|
||||
|
||||
public string? Email { get; set; }
|
||||
|
||||
public string? DocumenttypeName { get; set; }
|
||||
|
||||
public string? DocumentNumber { get; set; }
|
||||
|
||||
public string? Type { get; set; }
|
||||
|
||||
public int? SpecialtyId { get; set; }
|
||||
|
||||
public bool Active { get; set; }
|
||||
|
||||
public DateTime Createdat { get; set; }
|
||||
|
||||
public virtual PhSProfessionalSpecialty? Specialty { get; set; }
|
||||
}
|
||||
15
Models/Models/PhSProfessionalSpecialty.cs
Normal file
15
Models/Models/PhSProfessionalSpecialty.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhSProfessionalSpecialty
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
public bool Active { get; set; }
|
||||
|
||||
public virtual ICollection<PhSProfessional> PhSProfessionals { get; set; } = new List<PhSProfessional>();
|
||||
}
|
||||
@ -39,6 +39,10 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
|
||||
public virtual DbSet<PhSProductCategory> PhSProductCategories { get; set; }
|
||||
|
||||
public virtual DbSet<PhSProfessional> PhSProfessionals { get; set; }
|
||||
|
||||
public virtual DbSet<PhSProfessionalSpecialty> PhSProfessionalSpecialties { get; set; }
|
||||
|
||||
public virtual DbSet<PhSQuoteDetail> PhSQuoteDetails { get; set; }
|
||||
|
||||
public virtual DbSet<PhSQuoteHeader> PhSQuoteHeaders { get; set; }
|
||||
@ -52,7 +56,7 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
}
|
||||
}
|
||||
#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");
|
||||
//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)
|
||||
{
|
||||
@ -410,6 +414,78 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
.HasColumnName("name");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhSProfessional>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhS_Prof__3213E83F86B052AD");
|
||||
|
||||
entity.ToTable("PhS_Professionals");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Active)
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("active");
|
||||
entity.Property(e => e.Address)
|
||||
.HasMaxLength(250)
|
||||
.HasColumnName("address");
|
||||
entity.Property(e => e.City)
|
||||
.HasMaxLength(100)
|
||||
.HasColumnName("city");
|
||||
entity.Property(e => e.Createdat)
|
||||
.HasDefaultValueSql("(getdate())")
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("createdat");
|
||||
entity.Property(e => e.DocumentNumber)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("document_number");
|
||||
entity.Property(e => e.DocumenttypeName)
|
||||
.HasMaxLength(100)
|
||||
.HasColumnName("documenttype_name");
|
||||
entity.Property(e => e.Email)
|
||||
.HasMaxLength(150)
|
||||
.HasColumnName("email");
|
||||
entity.Property(e => e.Fullname)
|
||||
.HasMaxLength(200)
|
||||
.HasColumnName("fullname");
|
||||
entity.Property(e => e.License)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("license");
|
||||
entity.Property(e => e.Phone1)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("phone1");
|
||||
entity.Property(e => e.Phone2)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("phone2");
|
||||
entity.Property(e => e.Postalcode)
|
||||
.HasMaxLength(20)
|
||||
.HasColumnName("postalcode");
|
||||
entity.Property(e => e.Province)
|
||||
.HasMaxLength(100)
|
||||
.HasColumnName("province");
|
||||
entity.Property(e => e.SpecialtyId).HasColumnName("specialty_id");
|
||||
entity.Property(e => e.Type)
|
||||
.HasMaxLength(25)
|
||||
.HasColumnName("type");
|
||||
|
||||
entity.HasOne(d => d.Specialty).WithMany(p => p.PhSProfessionals)
|
||||
.HasForeignKey(d => d.SpecialtyId)
|
||||
.HasConstraintName("FK_Professionals_Specialty");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhSProfessionalSpecialty>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhS_Prof__3213E83F14C8B007");
|
||||
|
||||
entity.ToTable("PhS_ProfessionalSpecialties");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Active)
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("active");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(100)
|
||||
.HasColumnName("name");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhSQuoteDetail>(entity =>
|
||||
{
|
||||
entity.ToTable("PhS_QuoteDetails");
|
||||
|
||||
124
Models/Repositories/PhSProfessionalRepository.cs
Normal file
124
Models/Repositories/PhSProfessionalRepository.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Models.Helpers;
|
||||
using Models.Interfaces;
|
||||
using Models.Models;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Models.Repositories
|
||||
{
|
||||
public class PhSProfessionalRepository(PhronCareOperationsHubContext context) : IPhSProfessionalRepository
|
||||
{
|
||||
#region Declaraciones y Constructor
|
||||
private readonly PhronCareOperationsHubContext _context = context;
|
||||
#endregion
|
||||
#region Métodos de clase
|
||||
public async Task<PagedResult<EProfessional>> GetAllAsync(int page = 1, int pageSize = 50)
|
||||
{
|
||||
var query = _context.PhSProfessionals
|
||||
.Include(p => p.Specialty)
|
||||
.AsQueryable();
|
||||
|
||||
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
|
||||
|
||||
return new PagedResult<EProfessional>
|
||||
{
|
||||
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhSProfessional, EProfessional>),
|
||||
TotalItems = pagedEntities.TotalItems,
|
||||
Page = pagedEntities.Page,
|
||||
PageSize = pagedEntities.PageSize
|
||||
};
|
||||
}
|
||||
public async Task<EProfessional?> GetByIdAsync(int id)
|
||||
{
|
||||
var professional = await _context.PhSProfessionals
|
||||
.Include(p => p.Specialty)
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
|
||||
return professional != null ? EntityMapper.MapEntity<PhSProfessional, EProfessional>(professional) : null;
|
||||
}
|
||||
public async Task<PagedResult<EProfessional>> SearchAsync(string? fullname, string? document, string? type,
|
||||
int page = 1, int pageSize = 50)
|
||||
{
|
||||
var query = _context.PhSProfessionals
|
||||
.Include(p => p.Specialty)
|
||||
.AsQueryable();
|
||||
if (!string.IsNullOrWhiteSpace(fullname)) query = query.Where(c => c.Fullname.ToLower().Contains(fullname.ToLower()));
|
||||
if (!string.IsNullOrWhiteSpace(document) && document != "?")
|
||||
{
|
||||
query = query.Where(p =>
|
||||
EF.Functions.Like(p.DocumentNumber ?? "", $"%{document}%") ||
|
||||
EF.Functions.Like(p.License ?? "", $"%{document}%"));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(type)) query = query.Where(c => c.Type.ToLower().Contains(type.ToLower()));
|
||||
|
||||
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
|
||||
|
||||
return new PagedResult<EProfessional>
|
||||
{
|
||||
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhSProfessional, EProfessional>),
|
||||
TotalItems = pagedEntities.TotalItems,
|
||||
Page = pagedEntities.Page,
|
||||
PageSize = pagedEntities.PageSize
|
||||
};
|
||||
}
|
||||
public async Task<EProfessional> CreateAsync(EProfessional entity)
|
||||
{
|
||||
if (entity == null)
|
||||
throw new ArgumentNullException(nameof(entity), "El profesional no puede ser nulo.");
|
||||
|
||||
try
|
||||
{
|
||||
var dbEntity = EntityMapper.MapEntity<EProfessional, PhSProfessional>(entity);
|
||||
await _context.PhSProfessionals.AddAsync(dbEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return EntityMapper.MapEntity<PhSProfessional, EProfessional>(dbEntity);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
throw new Exception("Error al guardar el profesional en la base de datos.", dbEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Error inesperado al crear el profesional: " + ex.Message, ex);
|
||||
}
|
||||
}
|
||||
public async Task<bool> UpdateAsync(EProfessional entity)
|
||||
{
|
||||
if (entity == null)
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
|
||||
try
|
||||
{
|
||||
var existing = await _context.PhSProfessionals
|
||||
.FirstOrDefaultAsync(p => p.Id == entity.Id);
|
||||
|
||||
if (existing == null)
|
||||
return false;
|
||||
|
||||
EntityMapper.MapEntityToExisting(entity, existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
var entity = await _context.PhSProfessionals.FindAsync(id);
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
_context.PhSProfessionals.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
28
Models/Repositories/PhSProfessionalSpecialtyRepository.cs
Normal file
28
Models/Repositories/PhSProfessionalSpecialtyRepository.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Models.Helpers;
|
||||
using Models.Interfaces;
|
||||
using Models.Models;
|
||||
|
||||
namespace Models.Repositories
|
||||
{
|
||||
public class PhSProfessionalSpecialtyRepository(PhronCareOperationsHubContext context) : IPhSProfessionalSpecialtyRepository
|
||||
{
|
||||
#region Declaraciones y Constructor
|
||||
private readonly PhronCareOperationsHubContext _context = context;
|
||||
#endregion
|
||||
#region Metodos de clase
|
||||
public async Task<IEnumerable<EProfessionalSpecialty>> GetAllAsync()
|
||||
{
|
||||
var specialties = await _context.PhSProfessionalSpecialties.ToListAsync();
|
||||
return specialties.Select(EntityMapper.MapEntity<PhSProfessionalSpecialty, EProfessionalSpecialty>);
|
||||
}
|
||||
|
||||
public async Task<EProfessionalSpecialty?> GetByNameAsync(string name)
|
||||
{
|
||||
var professionalSpecialty = await _context.PhSProfessionalSpecialties.FirstOrDefaultAsync(a => a.Name.Contains(name));
|
||||
return professionalSpecialty != null ? EntityMapper.MapEntity<PhSProfessionalSpecialty, EProfessionalSpecialty>(professionalSpecialty) : null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
|
||||
|
||||
137
phronCare.API/Controllers/Sales/ProfessionalController.cs
Normal file
137
phronCare.API/Controllers/Sales/ProfessionalController.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Core.Interfaces;
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection;
|
||||
|
||||
namespace phronCare.API.Controllers.Sales
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ProfessionalController : ControllerBase
|
||||
{
|
||||
private readonly IProfessionalDom _professionalService;
|
||||
|
||||
public ProfessionalController(IProfessionalDom professionalService)
|
||||
{
|
||||
_professionalService = professionalService ?? throw new ArgumentNullException(nameof(professionalService));
|
||||
}
|
||||
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _professionalService.GetAllAsync(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<IActionResult> Search(
|
||||
[FromQuery] string? fullname,
|
||||
[FromQuery] string? document,
|
||||
[FromQuery] string? type,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _professionalService.SearchAsync(fullname, document, type, 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<ActionResult<EProfessional>> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var professional = await _professionalService.GetByIdAsync(id);
|
||||
if (professional == null)
|
||||
return NotFound();
|
||||
|
||||
return Ok(professional);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("create")]
|
||||
public async Task<IActionResult> Create([FromBody] EProfessional professional)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (professional == null)
|
||||
return BadRequest("El profesional no puede ser nulo.");
|
||||
|
||||
var result = await _professionalService.CreateAsync(professional);
|
||||
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<IActionResult> Update([FromBody] EProfessional professional)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (professional == null || professional.Id <= 0)
|
||||
return BadRequest("El profesional es inválido o no tiene un ID válido.");
|
||||
|
||||
var success = await _professionalService.UpdateAsync(professional);
|
||||
|
||||
if (!success)
|
||||
return NotFound($"No se encontró un profesional con ID {professional.Id}.");
|
||||
|
||||
return Ok("Profesional actualizado correctamente.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("exportfiltered")]
|
||||
public async Task<IActionResult> ExportFiltered([FromBody] ProfessionalSearchParams searchParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await _professionalService.ExportFilteredProfessionalsToExcelAsync(searchParams);
|
||||
return File(file,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"Profesionales.xlsx");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
using Core.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace phronCare.API.Controllers.Sales
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ProfessionalSpecialtyController : ControllerBase
|
||||
{
|
||||
private readonly IProfessionalSpecialtyDom _professionalSpecialtyService;
|
||||
public ProfessionalSpecialtyController(IProfessionalSpecialtyDom professionalSpecialtyService)
|
||||
{
|
||||
_professionalSpecialtyService = professionalSpecialtyService ?? throw new ArgumentNullException(nameof(professionalSpecialtyService));
|
||||
}
|
||||
[HttpGet("GetAll")]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _professionalSpecialtyService.GetAllAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
[HttpGet("GetByName/{name}")]
|
||||
public async Task<IActionResult> GetByName(string name)
|
||||
{
|
||||
var result = await _professionalSpecialtyService.GetByNameAsync(name);
|
||||
if (result == null)
|
||||
return NotFound($"No se encontró un tipo de cuenta con el nombre '{name}'.");
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -62,6 +62,12 @@ builder.Services.AddScoped<IPhSBusinessUnitRepository, PhSBusinessUnitRepository
|
||||
builder.Services.AddScoped<IPatientDom, PatientService>();
|
||||
builder.Services.AddScoped<IPhSPatientRepository, PhSPatientRepository>();
|
||||
|
||||
builder.Services.AddScoped<IProfessionalSpecialtyDom, ProfessionalSpecialtyService>();
|
||||
builder.Services.AddScoped<IPhSProfessionalSpecialtyRepository, PhSProfessionalSpecialtyRepository>();
|
||||
|
||||
builder.Services.AddScoped<IProfessionalDom, ProfessionalService>();
|
||||
builder.Services.AddScoped<IPhSProfessionalRepository, PhSProfessionalRepository>();
|
||||
|
||||
builder.Services.AddScoped<IInstitutionDom, InstitutionService>();
|
||||
builder.Services.AddScoped<IPhSInstitutionRepository, PhSInstitutionRepository>();
|
||||
#endregion
|
||||
|
||||
@ -1049,6 +1049,163 @@
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalController",
|
||||
"Method": "GetById",
|
||||
"RelativePath": "api/Professional/{id}",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": [
|
||||
{
|
||||
"Type": "Domain.Entities.EProfessional",
|
||||
"MediaTypes": [
|
||||
"text/plain",
|
||||
"application/json",
|
||||
"text/json"
|
||||
],
|
||||
"StatusCode": 200
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalController",
|
||||
"Method": "GetAll",
|
||||
"RelativePath": "api/Professional/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.ProfessionalController",
|
||||
"Method": "Create",
|
||||
"RelativePath": "api/Professional/create",
|
||||
"HttpMethod": "POST",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "professional",
|
||||
"Type": "Domain.Entities.EProfessional",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalController",
|
||||
"Method": "ExportFiltered",
|
||||
"RelativePath": "api/Professional/exportfiltered",
|
||||
"HttpMethod": "POST",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "searchParams",
|
||||
"Type": "Domain.Generics.ProfessionalSearchParams",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalController",
|
||||
"Method": "Search",
|
||||
"RelativePath": "api/Professional/search",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "fullname",
|
||||
"Type": "System.String",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Name": "document",
|
||||
"Type": "System.String",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Name": "type",
|
||||
"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.ProfessionalController",
|
||||
"Method": "Update",
|
||||
"RelativePath": "api/Professional/update",
|
||||
"HttpMethod": "PUT",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "professional",
|
||||
"Type": "Domain.Entities.EProfessional",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalSpecialtyController",
|
||||
"Method": "GetAll",
|
||||
"RelativePath": "api/ProfessionalSpecialty/GetAll",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalSpecialtyController",
|
||||
"Method": "GetByName",
|
||||
"RelativePath": "api/ProfessionalSpecialty/GetByName/{name}",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.TaxConditionController",
|
||||
"Method": "GetAll",
|
||||
|
||||
210
phronCare.UIBlazor/Pages/Sales/ProfessionalForm.razor
Normal file
210
phronCare.UIBlazor/Pages/Sales/ProfessionalForm.razor
Normal file
@ -0,0 +1,210 @@
|
||||
@page "/sales/professionalform/"
|
||||
@page "/sales/professionalform/{Id:int?}"
|
||||
@using phronCare.UIBlazor.Services.Sales
|
||||
@inject ProfessionalService ProfessionalService
|
||||
@inject DocumentTypeService documentTypeService
|
||||
@inject ProfessionalSpecialtyService specialtyService
|
||||
|
||||
|
||||
@inject NavigationManager Navigation
|
||||
@inject IToastService toastService
|
||||
|
||||
<EditForm Model="model" OnValidSubmit="HandleValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="card mt-4" style="zoom:80%">
|
||||
<div class="card-header d-flex justify-content-center align-items-center">
|
||||
<h3 class="card-title m-0">@((model.Id == 0) ? "Nuevo Profesional" : "Editar Profesional")</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label for="Fullname" class="form-label">Nombre Completo</label>
|
||||
<InputText id="Fullname" class="form-control" @bind-Value="model.Fullname" />
|
||||
<ValidationMessage For="@(() => model.Fullname)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="DocumentType" class="form-label">Tipo de documento:</label>
|
||||
<InputSelect id="DocumentType" @bind-Value="model.DocumenttypeName" class="form-control">
|
||||
<option value="">Seleccione un tipo</option>
|
||||
@foreach (var type in documentTypes)
|
||||
{
|
||||
<option value="@type.Code">@type.Description</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => model.DocumenttypeName)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="DocumentNumber" class="form-label">Número</label>
|
||||
<InputText id="DocumentNumber" class="form-control" @bind-Value="model.DocumentNumber" />
|
||||
<ValidationMessage For="@(() => model.DocumentNumber)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="License" class="form-label">Matrícula</label>
|
||||
<InputText id="License" class="form-control" @bind-Value="model.License" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="Phone1" class="form-label">Teléfono Principal</label>
|
||||
<InputText id="Phone1" class="form-control" @bind-Value="model.Phone1" />
|
||||
<ValidationMessage For="@(() => model.Phone1)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="Phone2" class="form-label">Teléfono Secundario</label>
|
||||
<InputText id="Phone2" class="form-control" @bind-Value="model.Phone2" />
|
||||
<ValidationMessage For="@(() => model.Phone2)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="Email" class="form-label">Email</label>
|
||||
<InputText id="Email" type="email" class="form-control" @bind-Value="model.Email" />
|
||||
<ValidationMessage For="@(() => model.Email)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="Type" class="form-label">Tipo de Profesional</label>
|
||||
<InputSelect id="Type" class="form-select" @bind-Value="model.Type">
|
||||
@foreach (var option in professionalTypes)
|
||||
{
|
||||
<option value="@option.Value">@option.Text</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => model.Type)" />
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="Specialtyid" class="form-label">Especialidad</label>
|
||||
<InputSelect id="Specialtyid" class="form-select" @bind-Value="model.SpecialtyId">
|
||||
<option value="">--- Seleccionar ---</option>
|
||||
@foreach (var s in specialties)
|
||||
{
|
||||
<option value="@s.Id">@s.Name</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => model.SpecialtyId)" />
|
||||
</div>
|
||||
<div class="col-md-1 mb-3 d-flex align-items-center justify-content-start mt-4">
|
||||
<div class="form-check form-switch">
|
||||
<InputCheckbox id="Isactive" @bind-Value="model.Active" class="form-check-input" />
|
||||
<label class="form-check-label ms-2" for="Isactive">Activo</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="Address" class="form-label">Dirección</label>
|
||||
<InputText id="Address" class="form-control" @bind-Value="model.Address" />
|
||||
<ValidationMessage For="@(() => model.Address)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="City" class="form-label">Ciudad</label>
|
||||
<InputText id="City" class="form-control" @bind-Value="model.City" />
|
||||
<ValidationMessage For="@(() => model.City)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="Province" class="form-label">Provincia:</label>
|
||||
<InputSelect id="Province" @bind-Value="model.Province" class="form-control">
|
||||
<option value="">--- Seleccionar ---</option>
|
||||
@foreach (var province in provinces)
|
||||
{
|
||||
<option value="@province">@province</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => model.Province)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-end align-items-center py-3">
|
||||
|
||||
<button type="submit" class="btn btn-primary me-2">Guardar</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int? Id { get; set; }
|
||||
|
||||
private EProfessional model = new();
|
||||
private List<EDocumentType> documentTypes = new();
|
||||
private List<EProfessionalSpecialty> specialties = new();
|
||||
|
||||
private List<string> provinces = new()
|
||||
{
|
||||
"Buenos Aires", "CABA", "Catamarca", "Chaco", "Chubut", "Córdoba", "Corrientes",
|
||||
"Entre Ríos", "Formosa", "Jujuy", "La Pampa", "La Rioja", "Mendoza", "Misiones",
|
||||
"Neuquén", "Río Negro", "Salta", "San Juan", "San Luis", "Santa Cruz",
|
||||
"Santa Fe", "Santiago del Estero", "Tierra del Fuego", "Tucumán"
|
||||
};
|
||||
private List<(string Value, string Text)> professionalTypes = new()
|
||||
{
|
||||
("","--- Seleccionar ---"),
|
||||
("Medico", "Médico"),
|
||||
("Instrumentador", "Instrumentador quirúrgico"),
|
||||
("Enfermero", "Enfermero/a"),
|
||||
("Tecnico", "Técnico quirúrgico")
|
||||
};
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadDocumentTypes();
|
||||
await LoadSpecialties();
|
||||
|
||||
|
||||
if (Id.HasValue && Id > 0)
|
||||
{
|
||||
var result = await ProfessionalService.GetById(Id.Value);
|
||||
if (result != null)
|
||||
model = result;
|
||||
else
|
||||
toastService.ShowError("No se pudo cargar el profesional.");
|
||||
}
|
||||
}
|
||||
private async Task LoadDocumentTypes()
|
||||
{
|
||||
documentTypes = await documentTypeService.GetAllAsync();
|
||||
}
|
||||
private async Task LoadSpecialties()
|
||||
{
|
||||
specialties = await specialtyService.GetAllAsync();
|
||||
}
|
||||
private async Task HandleValidSubmit()
|
||||
{
|
||||
var result = model.Id == 0
|
||||
? await ProfessionalService.CreateAsync(model)
|
||||
: await ProfessionalService.UpdateAsync(model);
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
toastService.ShowSuccess("Profesional guardado correctamente.");
|
||||
Navigation.NavigateTo("/sales/professionals");
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = await result.Content.ReadAsStringAsync();
|
||||
toastService.ShowError($"Error: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
Navigation.NavigateTo("/sales/professionals");
|
||||
}
|
||||
}
|
||||
210
phronCare.UIBlazor/Pages/Sales/Professionals.razor
Normal file
210
phronCare.UIBlazor/Pages/Sales/Professionals.razor
Normal file
@ -0,0 +1,210 @@
|
||||
@page "/sales/professionals"
|
||||
@using phronCare.UIBlazor.Services.Sales
|
||||
@using phronCare.UIBlazor.Data
|
||||
@using Domain.Entities
|
||||
@using Domain.Generics
|
||||
@inject IToastService toastService
|
||||
@inject NavigationManager Navigation
|
||||
@inject ProfessionalService professionalService
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-center align-items-center" style="zoom:80%;">
|
||||
<h3 class="card-title m-0">Búsqueda de profesionales</h3>
|
||||
</div>
|
||||
<div class="card-body" style="zoom:80%;">
|
||||
<div class="mb-4 space-y-2">
|
||||
<input @bind="SearchParams.Fullname" placeholder="Nombre y apellido" class="border rounded p-1 w-full" />
|
||||
<input @bind="SearchParams.Document" placeholder="Documento o Matricula" class="border rounded p-1 w-full" />
|
||||
<input @bind="SearchParams.Type" placeholder="Tipo de profesional" class="border rounded p-1 w-full" />
|
||||
<button class="btn btn-primary rounded-pill" @onclick="BuscarProfesionales">
|
||||
<i class="fas fa-binoculars me-1"></i> Buscar
|
||||
</button>
|
||||
<button class="btn btn-success rounded-pill" @onclick="NuevoProfesional">
|
||||
<i class="fas fa-plus me-1"></i> Nuevo
|
||||
</button>
|
||||
<button class="btn btn-success rounded-pill" @onclick="ExportarExcel">
|
||||
<i class="fas fa-file-excel me-1"></i> Excel
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="Cancel">
|
||||
<i class="fas fa-arrow-left me-1"></i> Volver
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
@if (TablaProfesionales != null && TablaProfesionales.Any())
|
||||
{
|
||||
<PhTable Columns="TableColumns"
|
||||
Data="TablaProfesionales"
|
||||
SelectionField="Id"
|
||||
RowsPerPage=SearchParams.PageSize
|
||||
RenderButtons="true" Buttons="botones"
|
||||
ShowPageButtons="false"
|
||||
ShowQuickSearch="false"
|
||||
RenderSelect="false" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>No hay resultados.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-center align-items-center" style="zoom:80%;">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="PrimeraPagina" disabled="@(SearchParams.Page == 1)">
|
||||
<i class="fas fa-angle-double-left me-1"></i> Primera
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="AnteriorPagina" disabled="@(!PuedeRetroceder)">
|
||||
<i class="fas fa-chevron-left me-1"></i> Anterior
|
||||
</button>
|
||||
<span class="mx-2">
|
||||
Página <strong>@SearchParams.Page</strong> de <strong>@TotalPaginas</strong>
|
||||
</span>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="SiguientePagina" disabled="@(!PuedeAvanzar)">
|
||||
Siguiente <i class="fas fa-chevron-right ms-1"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="UltimaPagina" disabled="@(SearchParams.Page == TotalPaginas)">
|
||||
Última <i class="fas fa-angle-double-right ms-1"></i>
|
||||
</button>
|
||||
<div class="d-flex align-items-center ms-3">
|
||||
<input type="number" class="form-control form-control-sm rounded" style="width: 80px;" min="1" max="@TotalPaginas" @bind="PaginaDeseada" />
|
||||
<button class="btn btn-outline-primary btn-sm ms-2 rounded-pill" @onclick="IrAPagina">
|
||||
<i class="fas fa-arrow-right-to-bracket me-1"></i> Ir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private ProfessionalSearchParams SearchParams = new();
|
||||
private PagedResult<EProfessional>? PagedResult;
|
||||
private List<Dictionary<string, object>> TablaProfesionales = new();
|
||||
private List<string> TableColumns = new()
|
||||
{
|
||||
"Id", "Nombre", "Tipo Documento", "N° Documento", "Tipo", "Especialidad",
|
||||
"Email", "Teléfono 1", "Teléfono 2", "Matrícula", "Activo"
|
||||
};
|
||||
private int PaginaDeseada = 1;
|
||||
private List<PhTable.ButtonOptions> botones = new();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
botones = new List<PhTable.ButtonOptions>
|
||||
{
|
||||
new PhTable.ButtonOptions
|
||||
{
|
||||
Caption = "Editar",
|
||||
ElementClass = "btn btn-primary btn-sm",
|
||||
UrlAction = "/sales/professionals/edit/",
|
||||
OnClickAction = async (id) =>
|
||||
{
|
||||
if (int.TryParse(id, out var profId))
|
||||
{
|
||||
Navigation.NavigateTo($"/sales/professionalform/{profId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task BuscarProfesionales()
|
||||
{
|
||||
await CargarProfesionales();
|
||||
}
|
||||
|
||||
private async Task CargarProfesionales()
|
||||
{
|
||||
PagedResult = await professionalService.SearchAsync(SearchParams);
|
||||
if (PagedResult?.Items is not null)
|
||||
{
|
||||
TablaProfesionales = PagedResult.Items.Select(p => new Dictionary<string, object>
|
||||
{
|
||||
{ "Id", p.Id },
|
||||
{ "Nombre", p.Fullname ?? string.Empty },
|
||||
{ "Tipo Documento", p.DocumenttypeName ?? string.Empty },
|
||||
{ "N° Documento", p.DocumentNumber ?? string.Empty },
|
||||
{ "Tipo", p.Type ?? string.Empty },
|
||||
{ "Especialidad", p.Specialty?.Name ?? string.Empty },
|
||||
{ "Email", p.Email ?? string.Empty },
|
||||
{ "Teléfono 1", p.Phone1 ?? string.Empty },
|
||||
{ "Teléfono 2", p.Phone2 ?? string.Empty },
|
||||
{ "Matrícula", p.License ?? string.Empty },
|
||||
{ "Activo", p.Active ? "Sí" : "No" },
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportarExcel()
|
||||
{
|
||||
var searchParams = new ProfessionalSearchParams
|
||||
{
|
||||
Fullname = SearchParams.Fullname,
|
||||
Document = SearchParams.Document,
|
||||
Type = SearchParams.Type,
|
||||
Page = 1,
|
||||
PageSize = int.MaxValue
|
||||
};
|
||||
try
|
||||
{
|
||||
await professionalService.ExportFilteredAsync(searchParams);
|
||||
toastService.ShowSuccess("Exportación completada exitosamente.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toastService.ShowError($"{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PrimeraPagina()
|
||||
{
|
||||
SearchParams.Page = 1;
|
||||
await BuscarProfesionales();
|
||||
}
|
||||
|
||||
private async Task UltimaPagina()
|
||||
{
|
||||
SearchParams.Page = TotalPaginas;
|
||||
await BuscarProfesionales();
|
||||
}
|
||||
|
||||
private async Task IrAPagina()
|
||||
{
|
||||
if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas)
|
||||
{
|
||||
SearchParams.Page = PaginaDeseada;
|
||||
await BuscarProfesionales();
|
||||
}
|
||||
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 BuscarProfesionales();
|
||||
}
|
||||
}
|
||||
|
||||
private int TotalPaginas => PagedResult is null ? 1 :
|
||||
(int)Math.Ceiling((double)(PagedResult.TotalItems) / SearchParams.PageSize);
|
||||
|
||||
private bool PuedeRetroceder => PagedResult != null && SearchParams.Page > 1;
|
||||
private bool PuedeAvanzar => PagedResult != null && SearchParams.Page < TotalPaginas;
|
||||
|
||||
private void NuevoProfesional()
|
||||
{
|
||||
Navigation.NavigateTo("/sales/professionalform/");
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
Navigation.NavigateTo("/DashboardPanel");
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,8 @@ builder.Services.AddScoped<DocumentTypeService>();
|
||||
builder.Services.AddScoped<InstitutionService>();
|
||||
builder.Services.AddScoped<ProductService>();
|
||||
builder.Services.AddScoped<BusinessUnitService>();
|
||||
builder.Services.AddScoped<ProfessionalService>();
|
||||
builder.Services.AddScoped<ProfessionalSpecialtyService>();
|
||||
builder.Services.AddScoped<ProductCategoryService>();
|
||||
#endregion
|
||||
#region UI
|
||||
|
||||
72
phronCare.UIBlazor/Services/Sales/ProfessionalService.cs
Normal file
72
phronCare.UIBlazor/Services/Sales/ProfessionalService.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Reflection;
|
||||
using System.Net.Http;
|
||||
using phronCare.UIBlazor.Pages.Sales;
|
||||
|
||||
namespace phronCare.UIBlazor.Services.Sales
|
||||
{
|
||||
public class ProfessionalService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly IJSRuntime _js;
|
||||
public ProfessionalService(HttpClient http, IJSRuntime js)
|
||||
{
|
||||
_http = http;
|
||||
_js = js;
|
||||
}
|
||||
public async Task<PagedResult<EProfessional>?> SearchAsync(ProfessionalSearchParams searchParams)
|
||||
{
|
||||
var url = $"api/Professional/search?" +
|
||||
$"fullname={searchParams.Fullname}&" +
|
||||
$"document={searchParams.Document}&" +
|
||||
$"type={searchParams.Type}&" +
|
||||
$"page={searchParams.Page}&" +
|
||||
$"pageSize={searchParams.PageSize}";
|
||||
return await _http.GetFromJsonAsync<PagedResult<EProfessional>>(url);
|
||||
}
|
||||
|
||||
public async Task<EProfessional?> GetById(int id)
|
||||
{
|
||||
return await _http.GetFromJsonAsync<EProfessional>($"/api/Professional/{id}") ;
|
||||
}
|
||||
public async Task<HttpResponseMessage> CreateAsync(EProfessional professional)
|
||||
{
|
||||
return await _http.PostAsJsonAsync("/api/Professional/Create", professional);
|
||||
}
|
||||
public async Task<HttpResponseMessage> UpdateAsync(EProfessional professional)
|
||||
{
|
||||
return await _http.PutAsJsonAsync("/api/Professional/Update", professional);
|
||||
}
|
||||
public async Task ExportFilteredAsync(ProfessionalSearchParams searchParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = new StringContent(JsonSerializer.Serialize(searchParams), Encoding.UTF8, "application/json");
|
||||
var response = await _http.PostAsync("api/Professional/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}_profesionales.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
using Domain.Entities;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace phronCare.UIBlazor.Services.Sales
|
||||
{
|
||||
public class ProfessionalSpecialtyService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
public ProfessionalSpecialtyService(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
public async Task<List<EProfessionalSpecialty>?> GetAllAsync()
|
||||
{
|
||||
return await _http.GetFromJsonAsync<List<EProfessionalSpecialty>>("api/ProfessionalSpecialty/GetAll");
|
||||
}
|
||||
public async Task<EProfessionalSpecialty?> GetByNameAsync(string name)
|
||||
{
|
||||
return await _http.GetFromJsonAsync<EProfessionalSpecialty>($"api/ProfessionalSpecialty/GetByName/{Uri.EscapeDataString(name)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,6 +81,11 @@
|
||||
<li aria-hidden="true"></li> Productos
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-1">
|
||||
<NavLink class="nav-link" href="sales/professionals/">
|
||||
<li aria-hidden="true"></li> Profesionales
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-1">
|
||||
<NavLink class="nav-link" href="sales/institutions/">
|
||||
<li aria-hidden="true"></li> Instituciones
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user