Update ADD Patients y Institutions Models in API
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m24s

This commit is contained in:
Leandro Hernan Rojas 2025-04-20 19:58:17 -03:00
parent 2b456f1e47
commit 8623488221
30 changed files with 1454 additions and 96 deletions

View File

@ -0,0 +1,21 @@
using Domain.Entities;
using Domain.Generics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.Interfaces
{
public interface IInstitutionDom
{
Task<EInstitution> CreateAsync(EInstitution entity);
Task<bool> DeleteAsync(int id);
Task<byte[]> ExportFilteredInstitutionsToExcelAsync(InstitutionSearchParams searchParams);
Task<PagedResult<EInstitution>> GetAllAsync(int page = 1, int pageSize = 50);
Task<EInstitution?> GetByIdAsync(int id);
Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50);
Task<bool> UpdateAsync(EInstitution entity);
}
}

View File

@ -0,0 +1,18 @@
using Domain.Entities;
using Domain.Generics;
using Domain.SearchParams;
namespace Core.Interfaces
{
public interface IPatientDom
{
Task<EPatient> CreateAsync(EPatient entity);
Task<bool> DeleteAsync(int id);
Task<byte[]> ExportFilteredPatientsToExcelAsync(PatientSearchParams searchParams);
Task<PagedResult<EPatient>> GetAllAsync(int page = 1, int pageSize = 50);
Task<EPatient?> GetByIdAsync(int id);
Task<PagedResult<EPatient>> SearchAsync(string? name, string? document, int page = 1, int pageSize = 50);
Task<bool> UpdateAsync(EPatient entity);
}
}

View File

@ -0,0 +1,124 @@
using Core.Interfaces;
using Domain.Entities;
using Domain.Generics;
using Models.Interfaces;
using System.Reflection;
using Transversal.Services;
namespace Core.Services
{
public class InstitutionService : IInstitutionDom
{
#region Declaraciones y Constructor
private readonly IPhSInstitutionRepository _repository;
public InstitutionService(IPhSInstitutionRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
#endregion
#region Métodos
public async Task<PagedResult<EInstitution>> GetAllAsync(int page = 1, int pageSize = 50)
{
try
{
return await _repository.GetAllAsync(page, pageSize);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
public async Task<EInstitution?> GetByIdAsync(int id)
{
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
public async Task<EInstitution> CreateAsync(EInstitution entity)
{
if (entity is null)
throw new ArgumentNullException(nameof(entity), "La institución no puede ser nula.");
if (string.IsNullOrWhiteSpace(entity.Name) )
throw new ArgumentException("Debe completar el nombre de la institución.");
if (string.IsNullOrWhiteSpace(entity.Type))
throw new ArgumentException("Debe seleccionar un tipo de institución.");
return await _repository.CreateAsync(entity);
}
public async Task<bool> UpdateAsync(EInstitution entity)
{
if (entity is null || entity.Id <= 0)
throw new ArgumentException("La institución es inválida o no tiene un ID válido.");
if (string.IsNullOrWhiteSpace(entity.Name))
throw new ArgumentException("Debe completar el nombre de la institución.");
if (string.IsNullOrWhiteSpace(entity.Type))
throw new ArgumentException("Debe seleccionar un tipo de institución.");
return await _repository.UpdateAsync(entity);
}
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50)
{
try
{
return await _repository.SearchAsync(name, city, province, page, pageSize);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
public Task<bool> DeleteAsync(int id)
{
throw new NotImplementedException();
}
public async Task<byte[]> ExportFilteredInstitutionsToExcelAsync(InstitutionSearchParams searchParams)
{
try
{
var searchResult = await SearchAsync(
searchParams.Name,
searchParams.City,
searchParams.Province,
searchParams.Page,
searchParams.PageSize
);
if (searchResult?.Items is null || !searchResult.Items.Any())
throw new Exception("No se encontraron instituciones para exportar.");
var stream = new XLSXExportBase();
var data = searchResult.Items.Select(i => new
{
i.Id,
i.Name,
i.Type,
i.City,
i.Province,
i.Email,
i.Phone,
i.Isactive
}).ToList();
return stream.ExportExcel(data);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
#endregion
}
}

View File

@ -0,0 +1,130 @@
using Core.Interfaces;
using Domain.Entities;
using Domain.Generics;
using Domain.SearchParams;
using Models.Interfaces;
using System.Reflection;
using Transversal.Services;
public class PatientService : IPatientDom
{
#region Declaraciones y Constructor
private readonly IPhSPatientRepository _repository;
public PatientService(IPhSPatientRepository patientRepository)
{
_repository = patientRepository ?? throw new ArgumentNullException(nameof(patientRepository));
}
#endregion
#region Métodos de servicio
public async Task<PagedResult<EPatient>> GetAllAsync(int page = 1, int pageSize = 50)
{
try
{
return await _repository.GetAllAsync(page, pageSize);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
public async Task<EPatient?> GetByIdAsync(int id)
{
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
public async Task<EPatient> CreateAsync(EPatient entity)
{
if (entity is null)
throw new ArgumentNullException(nameof(entity), "El paciente no puede ser nulo.");
if (string.IsNullOrWhiteSpace(entity.Firstname) || string.IsNullOrWhiteSpace(entity.Lastname))
throw new ArgumentException("Debe completar el nombre y apellido del paciente.");
if (string.IsNullOrWhiteSpace(entity.DocumentNumber))
throw new ArgumentException("Debe completar el número de documento del paciente.");
return await _repository.CreateAsync(entity);
}
public async Task<bool> UpdateAsync(EPatient entity)
{
if (entity is null)
throw new ArgumentNullException(nameof(entity), "El paciente no puede ser nulo.");
if (string.IsNullOrWhiteSpace(entity.Firstname) || string.IsNullOrWhiteSpace(entity.Lastname))
throw new ArgumentException("Debe completar el nombre y apellido del paciente.");
if (string.IsNullOrWhiteSpace(entity.DocumentNumber))
throw new ArgumentException("Debe completar el número de documento del paciente.");
return await _repository.UpdateAsync(entity);
}
public async Task<PagedResult<EPatient>> SearchAsync(string? name, string? document, int page = 1, int pageSize = 50)
{
try
{
return await _repository.SearchAsync(name, document, page, pageSize);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} Message: {ex.Message}", ex);
}
}
public Task<bool> DeleteAsync(int id)
{
// Implementar según políticas del sistema (soft delete, etc.)
throw new NotImplementedException();
}
public async Task<byte[]> ExportFilteredPatientsToExcelAsync(PatientSearchParams searchParams)
{
try
{
var searchResult = await SearchAsync(
searchParams.Name,
searchParams.Document,
searchParams.Page,
searchParams.PageSize
);
if (searchResult?.Items is null || !searchResult.Items.Any())
{
throw new Exception("No se encontraron pacientes para exportar.");
}
var stream = new XLSXExportBase();
var patientData = searchResult.Items.Select(p => new
{
p.Id,
p.Firstname,
p.Lastname,
p.DocumentNumber,
p.AffiliateNumber,
p.Email,
p.Phone,
p.Birthdate,
p.Gender
}).ToList();
var excelFile = stream.ExportExcel(patientData);
return excelFile;
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
throw new Exception($"{methodName} - Error al exportar pacientes: {ex.Message}", ex);
}
}
#endregion
}

View File

@ -0,0 +1,20 @@
namespace Domain.Entities
{
public partial class EInstitution
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string Type { get; set; } = null!;
public string? Streetaddress { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Operatingroominfo { get; set; }
public DateTime Createdat { get; set; }
public bool Isactive { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
namespace Domain.Entities
{
public class EPatient
{
public int Id { get; set; }
[Required(ErrorMessage = "Debe ingresar un nombre.")]
public string Firstname { get; set; } = null!;
public string Lastname { get; set; } = null!;
public int? DocumenttypesId { get; set; }
public string? DocumentNumber { get; set; }
public string? AffiliateNumber { get; set; }
public DateOnly? Birthdate { get; set; }
public string? Gender { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Address { get; set; }
public string? Notes { get; set; }
public int? CustomerId { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
}
}

View File

@ -1,13 +1,14 @@
namespace Domain.Entities
using System.ComponentModel.DataAnnotations;
namespace Domain.Entities
{
public class EProduct
{
public int Id { get; set; }
public int? BusinessunitsId { get; set; }
[Required(ErrorMessage = "Debe ingresar un nombre.")]
public string? Name { get; set; }
[Required(ErrorMessage = "Debe ingresar una descripcion.")]
public string Description { get; set; } = null!;
public int? Categoryid { get; set; }

View File

@ -0,0 +1,9 @@
namespace Domain.Generics
{
public class InstitutionSearchParams :PagedRequest
{
public string? Name { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using Domain.Generics;
namespace Domain.SearchParams
{
public class PatientSearchParams : PagedRequest
{
public string? Name { get; set; }
public string? Document { get; set; }
}
}

View File

@ -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\" />

View File

@ -0,0 +1,19 @@
using Domain.Entities;
using Domain.Generics;
namespace Models.Interfaces
{
public interface IPhSInstitutionRepository
{
Task<EInstitution> CreateAsync(EInstitution model);
Task<PagedResult<EInstitution>> GetAllAsync(int page = 1, int pageSize = 50);
Task<EInstitution?> GetByIdAsync(int id);
Task<PagedResult<EInstitution>> SearchAsync(
string? name,
string? city,
string? province,
int page = 1,
int pageSize = 50);
Task<bool> UpdateAsync(EInstitution model);
}
}

View File

@ -0,0 +1,20 @@
using Domain.Entities;
using Domain.Generics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Models.Interfaces
{
public interface IPhSPatientRepository
{
Task<EPatient> CreateAsync(EPatient entity);
Task<bool> DeleteAsync(int id);
Task<PagedResult<EPatient>> GetAllAsync(int page = 1, int pageSize = 50);
Task<EPatient?> GetByIdAsync(int id);
Task<PagedResult<EPatient>> SearchAsync(string? name, string? document, int page = 1, int pageSize = 50);
Task<bool> UpdateAsync(EPatient entity);
}
}

View File

@ -1,4 +1,7 @@
namespace Models.Models;
using System;
using System.Collections.Generic;
namespace Models.Models;
public partial class PhSBusinessUnit
{

View File

@ -29,6 +29,8 @@ public partial class PhSCustomer
public virtual ICollection<PhSCustomerDocument> PhSCustomerDocuments { get; set; } = new List<PhSCustomerDocument>();
public virtual ICollection<PhSPatient> PhSPatients { get; set; } = new List<PhSPatient>();
public virtual ICollection<PhSQuoteHeader> PhSQuoteHeaders { get; set; } = new List<PhSQuoteHeader>();
public virtual PhOhTaxCondition? TaxCondition { get; set; }

View File

@ -14,4 +14,6 @@ public partial class PhSDocumentType
public string? Description { get; set; }
public virtual ICollection<PhSCustomerDocument> PhSCustomerDocuments { get; set; } = new List<PhSCustomerDocument>();
public virtual ICollection<PhSPatient> PhSPatients { get; set; } = new List<PhSPatient>();
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
namespace Models.Models;
public partial class PhSInstitution
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string Type { get; set; } = null!;
public string? Streetaddress { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Operatingroominfo { get; set; }
public DateTime Createdat { get; set; }
public bool Isactive { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
namespace Models.Models;
public partial class PhSPatient
{
public int Id { get; set; }
public string Firstname { get; set; } = null!;
public string Lastname { get; set; } = null!;
public int? DocumenttypesId { get; set; }
public string? DocumentNumber { get; set; }
public string? AffiliateNumber { get; set; }
public DateOnly? Birthdate { get; set; }
public string? Gender { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Address { get; set; }
public string? Notes { get; set; }
public int? CustomerId { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
public virtual PhSCustomer? Customer { get; set; }
public virtual PhSDocumentType? Documenttypes { get; set; }
}

View File

@ -1,4 +1,7 @@
namespace Models.Models;
using System;
using System.Collections.Generic;
namespace Models.Models;
public partial class PhSProductCategory
{

View File

@ -1,4 +1,7 @@
namespace Models.Models;
using System;
using System.Collections.Generic;
namespace Models.Models;
public partial class PhSQuoteDetail
{

View File

@ -31,6 +31,10 @@ public partial class PhronCareOperationsHubContext : DbContext
public virtual DbSet<PhSDocumentType> PhSDocumentTypes { get; set; }
public virtual DbSet<PhSInstitution> PhSInstitutions { get; set; }
public virtual DbSet<PhSPatient> PhSPatients { get; set; }
public virtual DbSet<PhSProduct> PhSProducts { get; set; }
public virtual DbSet<PhSProductCategory> PhSProductCategories { get; set; }
@ -259,6 +263,98 @@ public partial class PhronCareOperationsHubContext : DbContext
.HasColumnName("name");
});
modelBuilder.Entity<PhSInstitution>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__PhS_Inst__3213E83F7645655B");
entity.ToTable("PhS_Institutions");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.City)
.HasMaxLength(100)
.HasColumnName("city");
entity.Property(e => e.Createdat)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("createdat");
entity.Property(e => e.Email)
.HasMaxLength(150)
.HasColumnName("email");
entity.Property(e => e.Isactive)
.HasDefaultValue(true)
.HasColumnName("isactive");
entity.Property(e => e.Name)
.HasMaxLength(200)
.HasColumnName("name");
entity.Property(e => e.Operatingroominfo)
.HasMaxLength(200)
.HasColumnName("operatingroominfo");
entity.Property(e => e.Phone)
.HasMaxLength(50)
.HasColumnName("phone");
entity.Property(e => e.Province)
.HasMaxLength(100)
.HasColumnName("province");
entity.Property(e => e.Streetaddress)
.HasMaxLength(250)
.HasColumnName("streetaddress");
entity.Property(e => e.Type)
.HasMaxLength(15)
.HasColumnName("type");
});
modelBuilder.Entity<PhSPatient>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__PhS_Pati__3213E83F93A6EB7E");
entity.ToTable("PhS_Patients");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Address)
.HasMaxLength(250)
.HasColumnName("address");
entity.Property(e => e.AffiliateNumber)
.HasMaxLength(100)
.HasColumnName("affiliate_number");
entity.Property(e => e.Birthdate).HasColumnName("birthdate");
entity.Property(e => e.Createdat)
.HasDefaultValueSql("(getdate())")
.HasColumnType("datetime")
.HasColumnName("createdat");
entity.Property(e => e.CustomerId).HasColumnName("customer_id");
entity.Property(e => e.DocumentNumber)
.HasMaxLength(50)
.HasColumnName("document_number");
entity.Property(e => e.DocumenttypesId).HasColumnName("documenttypes_id");
entity.Property(e => e.Email)
.HasMaxLength(150)
.HasColumnName("email");
entity.Property(e => e.Firstname)
.HasMaxLength(100)
.HasColumnName("firstname");
entity.Property(e => e.Gender)
.HasMaxLength(10)
.HasColumnName("gender");
entity.Property(e => e.Lastname)
.HasMaxLength(100)
.HasColumnName("lastname");
entity.Property(e => e.Modifiedat)
.HasColumnType("datetime")
.HasColumnName("modifiedat");
entity.Property(e => e.Notes).HasColumnName("notes");
entity.Property(e => e.Phone)
.HasMaxLength(50)
.HasColumnName("phone");
entity.HasOne(d => d.Customer).WithMany(p => p.PhSPatients)
.HasForeignKey(d => d.CustomerId)
.HasConstraintName("FK_Patients_Customers");
entity.HasOne(d => d.Documenttypes).WithMany(p => p.PhSPatients)
.HasForeignKey(d => d.DocumenttypesId)
.HasConstraintName("FK_Patients_DocumentTypes");
});
modelBuilder.Entity<PhSProduct>(entity =>
{
entity.ToTable("PhS_Products");

View File

@ -13,6 +13,7 @@ namespace Models.Repositories
#region Declaraciones y Constructor
private readonly PhronCareOperationsHubContext _context = context;
#endregion
#region Métodos de clase
public async Task<PagedResult<ECustomer>> GetAllAsync(int page = 1, int pageSize = 50)
{
var query = _context.PhSCustomers
@ -176,5 +177,6 @@ namespace Models.Repositories
await _context.SaveChangesAsync();
return true;
}
#endregion
}
}

View File

@ -0,0 +1,111 @@
using Microsoft.EntityFrameworkCore;
using Models.Interfaces;
using Domain.Entities;
using Models.Helpers;
using Models.Models;
using Domain.Generics;
namespace Models.Repositories
{
public class PhSInstitutionRepository(PhronCareOperationsHubContext context):IPhSInstitutionRepository
{
#region Declaraciones y Constructor
private readonly PhronCareOperationsHubContext _context = context;
#endregion
#region Metodos de clase
public async Task<PagedResult<EInstitution>> GetAllAsync(int page = 1, int pageSize = 50)
{
var query = _context.PhSInstitutions.AsNoTracking().OrderBy(x => x.Name);
var totalItems = await query.CountAsync();
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
return new PagedResult<EInstitution>
{
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhSInstitution, EInstitution>),
TotalItems = pagedEntities.TotalItems,
Page = pagedEntities.Page,
PageSize = pagedEntities.PageSize
};
}
public async Task<EInstitution?> GetByIdAsync(int id)
{
var entity = await _context.PhSInstitutions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
return entity == null ? null : EntityMapper.MapEntity<PhSInstitution, EInstitution>(entity);
}
public async Task<PagedResult<EInstitution>> SearchAsync(string? name, string? city, string? province, int page = 1, int pageSize = 50)
{
var query = _context.PhSInstitutions.AsQueryable();
if (!string.IsNullOrWhiteSpace(name))
{
var lowered = name.ToLower();
query = query.Where(i => i.Name.ToLower().Contains(lowered));
}
if (!string.IsNullOrWhiteSpace(city))
{
var lowered = city.ToLower();
query = query.Where(i => i.City != null && i.City.ToLower().Contains(lowered));
}
if (!string.IsNullOrWhiteSpace(province))
{
var lowered = province.ToLower();
query = query.Where(i => i.Province != null && i.Province.ToLower().Contains(lowered));
}
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
return new PagedResult<EInstitution>
{
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhSInstitution, EInstitution>),
TotalItems = pagedEntities.TotalItems,
Page = pagedEntities.Page,
PageSize = pagedEntities.PageSize
};
}
public async Task<EInstitution> CreateAsync(EInstitution model)
{
if (model == null)
throw new ArgumentNullException(nameof(model), "La insitucion no puede ser nula.");
try
{
var entity = EntityMapper.MapEntity<EInstitution, PhSInstitution>(model);
_context.PhSInstitutions.Add(entity);
await _context.SaveChangesAsync();
return EntityMapper.MapEntity<PhSInstitution, EInstitution>(entity);
}
catch (DbUpdateException dbEx)
{
throw new Exception("Error al guardar la institucion en la base de datos.", dbEx);
}
catch (Exception ex)
{
throw new Exception("Error inesperado al crear la institucion: " + ex.Message, ex);
}
}
public async Task<bool> UpdateAsync(EInstitution model)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
try
{
var existingInstitution = await _context.PhSInstitutions.FirstOrDefaultAsync(x => x.Id == model.Id);
if (existingInstitution == null)
return false;
EntityMapper.MapEntityToExisting(model, existingInstitution); // Actualiza campos
await _context.SaveChangesAsync();
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
}
}

View File

@ -0,0 +1,133 @@
using Domain.Entities;
using Domain.Generics;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Models.Helpers;
using Models.Interfaces;
using Models.Models;
namespace Infrastructure.Repositories.Patients
{
public class PhSPatientRepository(PhronCareOperationsHubContext context, ILogger<PhSPatientRepository> logger) : IPhSPatientRepository
{
#region Declaraciones y Constructor
private readonly PhronCareOperationsHubContext _context = context;
#endregion
#region Métodos de clase
public async Task<PagedResult<EPatient>> GetAllAsync(int page = 1, int pageSize = 50)
{
var query = _context.PhSPatients
.Include(p => p.Customer)
.Include(p => p.Documenttypes)
.AsQueryable();
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
return new PagedResult<EPatient>
{
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhSPatient, EPatient>),
TotalItems = pagedEntities.TotalItems,
Page = pagedEntities.Page,
PageSize = pagedEntities.PageSize
};
}
public async Task<EPatient?> GetByIdAsync(int id)
{
var patient = await _context.PhSPatients
.Include(p => p.Customer)
.Include(p => p.Documenttypes)
.FirstOrDefaultAsync(p => p.Id == id);
return patient != null ? EntityMapper.MapEntity<PhSPatient, EPatient>(patient) : null;
}
public async Task<PagedResult<EPatient>> SearchAsync(
string? name,
string? document,
int page = 1,
int pageSize = 50)
{
var query = _context.PhSPatients
.Include(p => p.Documenttypes)
.Include(p => p.Customer)
.AsQueryable();
if (!string.IsNullOrWhiteSpace(name))
{
var lowered = name.ToLower();
query = query.Where(p =>
p.Firstname.ToLower().Contains(lowered) ||
p.Lastname.ToLower().Contains(lowered));
}
if (!string.IsNullOrWhiteSpace(document))
{
query = query.Where(p =>
EF.Functions.Like(p.DocumentNumber!, $"%{document}%") || EF.Functions.Like(p.AffiliateNumber!, $"%{document}%"));
}
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
return new PagedResult<EPatient>
{
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhSPatient, EPatient>),
TotalItems = pagedEntities.TotalItems,
Page = pagedEntities.Page,
PageSize = pagedEntities.PageSize
};
}
public async Task<EPatient> CreateAsync(EPatient entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity), "El paciente no puede ser nulo.");
try
{
var patient = EntityMapper.MapEntity<EPatient, PhSPatient>(entity);
await _context.PhSPatients.AddAsync(patient);
await _context.SaveChangesAsync();
return EntityMapper.MapEntity<PhSPatient, EPatient>(patient);
}
catch (DbUpdateException dbEx)
{
throw new Exception("Error al guardar el paciente en la base de datos.", dbEx);
}
catch (Exception ex)
{
throw new Exception("Error inesperado al crear el paciente: " + ex.Message, ex);
}
}
public async Task<bool> UpdateAsync(EPatient entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
var existingPatient = await _context.PhSPatients
.FirstOrDefaultAsync(p => p.Id == entity.Id);
if (existingPatient == null)
return false;
EntityMapper.MapEntityToExisting(entity, existingPatient);
await _context.SaveChangesAsync();
return true;
}
catch (Exception)
{
return false;
}
}
public async Task<bool> DeleteAsync(int id)
{
var patient = await _context.PhSPatients.FindAsync(id);
if (patient == null) return false;
_context.PhSPatients.Remove(patient);
await _context.SaveChangesAsync();
return true;
}
#endregion
}
}

View File

@ -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\" />

View File

@ -0,0 +1,130 @@
using Core.Interfaces;
using Domain.Entities;
using Domain.Generics;
using Domain.SearchParams;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
namespace phronCare.API.Controllers.Sales
{
[Route("api/[controller]")]
[ApiController]
public class InstitutionController : ControllerBase
{
private readonly IInstitutionDom _institutionService;
public InstitutionController(IInstitutionDom institutionService)
{
_institutionService = institutionService ?? throw new ArgumentNullException(nameof(institutionService));
}
[HttpGet("all")]
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50)
{
try
{
var result = await _institutionService.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? name,
[FromQuery] string? city,
[FromQuery] string? province,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
try
{
var result = await _institutionService.SearchAsync(name, city, province, 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<EInstitution>> GetById(int id)
{
try
{
var institution = await _institutionService.GetByIdAsync(id);
if (institution == null)
return NotFound();
return Ok(institution);
}
catch (Exception ex)
{
return StatusCode(500, $"Error: {ex.Message}");
}
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody] EInstitution institution)
{
try
{
if (institution == null)
return BadRequest("La institución no puede ser nula.");
var result = await _institutionService.CreateAsync(institution);
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] EInstitution institution)
{
try
{
if (institution == null || institution.Id <= 0)
return BadRequest("La institución es inválida o no tiene un ID válido.");
var success = await _institutionService.UpdateAsync(institution);
if (!success)
return NotFound($"No se encontró una institución con ID {institution.Id}.");
return Ok("Institución actualizada 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] InstitutionSearchParams searchParams)
{
try
{
var file = await _institutionService.ExportFilteredInstitutionsToExcelAsync(searchParams);
return File(file,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Instituciones.xlsx");
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
}

View File

@ -0,0 +1,135 @@
using Core.Interfaces;
using Domain.Entities;
using Domain.SearchParams;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
namespace phronCare.API.Controllers.Sales
{
[Route("api/[controller]")]
[ApiController]
public class PatientController : ControllerBase
{
private readonly IPatientDom _patientService;
public PatientController(IPatientDom patientService)
{
_patientService = patientService ?? throw new ArgumentNullException(nameof(patientService));
}
[HttpGet("all")]
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50)
{
try
{
var result = await _patientService.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? name,
[FromQuery] string? document,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
try
{
var result = await _patientService.SearchAsync(name, document, 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<EPatient>> GetById(int id)
{
try
{
var patient = await _patientService.GetByIdAsync(id);
if (patient == null)
return NotFound();
return Ok(patient);
}
catch (Exception ex)
{
return StatusCode(500, $"Error: {ex.Message}");
}
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody] EPatient patient)
{
try
{
if (patient == null)
return BadRequest("El paciente no puede ser nulo.");
var result = await _patientService.CreateAsync(patient);
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] EPatient patient)
{
try
{
if (patient == null || patient.Id <= 0)
return BadRequest("El paciente es inválido o no tiene un ID válido.");
var success = await _patientService.UpdateAsync(patient);
if (!success)
return NotFound($"No se encontró un paciente con ID {patient.Id}.");
return Ok("Paciente 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] PatientSearchParams searchParams)
{
try
{
var file = await _patientService.ExportFilteredPatientsToExcelAsync(searchParams);
return File(file,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Pacientes.xlsx");
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
}

View File

@ -14,6 +14,7 @@ using Services.Models;
using Services.Services;
using Services.Interfaces;
using System.Text;
using Infrastructure.Repositories.Patients;
var builder = WebApplication.CreateBuilder(args);
@ -58,6 +59,11 @@ builder.Services.AddScoped<IPhSProductCategoryRepository, PhSProductCategoryRepo
builder.Services.AddScoped<IBusinessUnitDom, BusinessUnitService>();
builder.Services.AddScoped<IPhSBusinessUnitRepository, PhSBusinessUnitRepository>();
builder.Services.AddScoped<IPatientDom, PatientService>();
builder.Services.AddScoped<IPhSPatientRepository, PhSPatientRepository>();
builder.Services.AddScoped<IInstitutionDom, InstitutionService>();
builder.Services.AddScoped<IPhSInstitutionRepository, PhSInstitutionRepository>();
#endregion
#region Require Confirmed Email

View File

@ -571,6 +571,263 @@
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.InstitutionController",
"Method": "GetById",
"RelativePath": "api/Institution/{id}",
"HttpMethod": "GET",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.Int32",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "Domain.Entities.EInstitution",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
},
{
"ContainingType": "phronCare.API.Controllers.Sales.InstitutionController",
"Method": "GetAll",
"RelativePath": "api/Institution/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.InstitutionController",
"Method": "Create",
"RelativePath": "api/Institution/create",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "institution",
"Type": "Domain.Entities.EInstitution",
"IsRequired": true
}
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.InstitutionController",
"Method": "ExportFiltered",
"RelativePath": "api/Institution/exportfiltered",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "searchParams",
"Type": "Domain.Generics.InstitutionSearchParams",
"IsRequired": true
}
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.InstitutionController",
"Method": "Search",
"RelativePath": "api/Institution/search",
"HttpMethod": "GET",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "name",
"Type": "System.String",
"IsRequired": false
},
{
"Name": "city",
"Type": "System.String",
"IsRequired": false
},
{
"Name": "province",
"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.InstitutionController",
"Method": "Update",
"RelativePath": "api/Institution/update",
"HttpMethod": "PUT",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "institution",
"Type": "Domain.Entities.EInstitution",
"IsRequired": true
}
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.PatientController",
"Method": "GetById",
"RelativePath": "api/Patient/{id}",
"HttpMethod": "GET",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.Int32",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "Domain.Entities.EPatient",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
},
{
"ContainingType": "phronCare.API.Controllers.Sales.PatientController",
"Method": "GetAll",
"RelativePath": "api/Patient/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.PatientController",
"Method": "Create",
"RelativePath": "api/Patient/create",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "patient",
"Type": "Domain.Entities.EPatient",
"IsRequired": true
}
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.PatientController",
"Method": "ExportFiltered",
"RelativePath": "api/Patient/exportfiltered",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "searchParams",
"Type": "Domain.SearchParams.PatientSearchParams",
"IsRequired": true
}
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.PatientController",
"Method": "Search",
"RelativePath": "api/Patient/search",
"HttpMethod": "GET",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "name",
"Type": "System.String",
"IsRequired": false
},
{
"Name": "document",
"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.PatientController",
"Method": "Update",
"RelativePath": "api/Patient/update",
"HttpMethod": "PUT",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "patient",
"Type": "Domain.Entities.EPatient",
"IsRequired": true
}
],
"ReturnTypes": []
},
{
"ContainingType": "phronCare.API.Controllers.Sales.ProductController",
"Method": "GetById",

View File

@ -52,17 +52,21 @@
</div>
</div>
<div class="row">
<div class="col-md-4">
<label for="HasCreditAccount">¿Cuenta Corriente?</label><br />
<InputCheckbox id="HasCreditAccount" @bind-Value="customer.HasCreditAccount" />
<div class="col-md-4 d-flex align-items-center justify-content-start mt-4">
<div class="form-check form-switch">
<InputCheckbox id="HasCreditAccount" @bind-Value="customer.HasCreditAccount" class="form-check-input" />
<label class="form-check-label ms-2" for="HasCreditAccount">¿Cuenta Corriente?</label>
</div>
</div>
<div class="col-md-4">
<label for="CreditLimit">Límite de Crédito:</label>
<InputNumber id="CreditLimit" @bind-Value="customer.CreditLimit" class="form-control" />
</div>
<div class="col-md-4">
<label for="Active">Activo:</label><br />
<InputCheckbox id="Active" @bind-Value="customer.Active" />
<div class="col-md-4 d-flex align-items-center justify-content-start mt-4">
<div class="form-check form-switch">
<InputCheckbox id="Active" @bind-Value="customer.Active" class="form-check-input" />
<label class="form-check-label ms-2" for="Active">Activo</label>
</div>
</div>
</div>
<hr />
@ -71,7 +75,7 @@
<div class="col-sm-4">
<label for="TipoDocumento">Tipo:</label>
<InputSelect id="TipoDocumento" class="form-control" @bind-Value="documentFormModel.DocumenttypesId">
<option value="">Seleccione</option>
<option value="">-- Seleccionar --</option>
@foreach (var tipo in documentTypes)
{
<option value="@tipo.Id">@tipo.Name</option>
@ -83,7 +87,7 @@
<InputText id="NumeroDocumento" class="form-control" @bind-Value="documentFormModel.DocumentNumber" />
</div>
<div class="col-sm-2">
<button type="button" class="btn btn-success" @onclick="AddCustomerDocument">Agregar</button>
<button type="button" class="btn btn-sm btn-success" @onclick="AddCustomerDocument">Agregar documento</button>
</div>
</div>
<br />
@ -113,6 +117,38 @@
}
<hr />
<h5>Direcciones</h5>
@if (customer.PhSCustomerAddresses.Any())
{
<table class="table table-bordered">
<thead>
<tr>
<th>Sucursal</th>
<th>Dirección</th>
<th>Ciudad</th>
<th>Provincia</th>
<th>País</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var address in customer.PhSCustomerAddresses.Select((value, index) => new { value, index }))
{
<tr>
<td>@address.value.BusinessName</td>
<td>@address.value.Streetaddress1</td>
<td>@address.value.City</td>
<td>@address.value.Stateprovince</td>
<td>@address.value.Country</td>
<td>
<button type="button" class="btn btn-sm btn-primary me-1" @onclick="() => EditAddress(address.index)">Editar</button>
<button type="button" class="btn btn-sm btn-danger" @onclick="() => RemoveAddress(address.index)">Eliminar</button>
</td>
</tr>
}
</tbody>
</table>
}
<div class="row align-items-end">
<div class="row mb-3">
<div class="col-md-6">
<input class="form-control" placeholder="Nombre de sucursal" @bind="editingAddress.BusinessName" />
@ -167,63 +203,31 @@
<input class="form-control" placeholder="Email" @bind="editingAddress.Email" />
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<div class="row mb-3 row align-items-end align-items-center">
<div class="col-sm-8">
<textarea class="form-control" placeholder="Notas" @bind="editingAddress.Notes"></textarea>
</div>
</div>
<div class="mb-3">
<div class="col-sm-2">
<button type="button" class="btn btn-sm btn-success" @onclick="AddOrUpdateAddress">
@((editingIndex == -1) ? "Agregar dirección" : "Actualizar dirección")
</button>
</div>
</div>
</div>
<div class="mb-3">
@if (editingIndex != -1)
{
<button type="button" class="btn btn-sm btn-secondary ms-2" @onclick="CancelAddressEdit">Cancelar</button>
}
</div>
@if (customer.PhSCustomerAddresses.Any())
{
<table class="table table-bordered">
<thead>
<tr>
<th>Sucursal</th>
<th>Dirección</th>
<th>Ciudad</th>
<th>Provincia</th>
<th>País</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var address in customer.PhSCustomerAddresses.Select((value, index) => new { value, index }))
{
<tr>
<td>@address.value.BusinessName</td>
<td>@address.value.Streetaddress1</td>
<td>@address.value.City</td>
<td>@address.value.Stateprovince</td>
<td>@address.value.Country</td>
<td>
<button type="button" class="btn btn-sm btn-primary me-1" @onclick="() => EditAddress(address.index)">Editar</button>
<button type="button" class="btn btn-sm btn-danger" @onclick="() => RemoveAddress(address.index)">Eliminar</button>
</td>
</tr>
}
</tbody>
</table>
}
</EditForm>
</div>
<div class="card-footer">
<div class="row">
<div class="col">
<div class="mt-4">
<button class="btn btn-primary" type="button" @onclick="HandleValidSubmit" disabled="@isSaving">
@(isSaving ? "Guardando..." : "Guardar Cliente")
</button>
</div>
</div>
<div class="d-flex justify-content-end align-items-center py-3">
<button class="btn btn-primary me-2" type="button" @onclick="HandleValidSubmit" disabled="@isSaving">
@(isSaving ? "Guardando..." : "Guardar Cliente") </button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
</div>
</div>
</div>

View File

@ -98,7 +98,7 @@
</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 class="btn btn-primary me-2" type="button" @onclick="HandleValidSubmit" disabled="@isSaving"> @(isSaving ? "Guardando..." : "Guardar producto") </button>
<button type="button" class="btn btn-secondary" @onclick="NavigateBack">Cancelar</button>
</div>
</div>
@ -112,6 +112,7 @@
private EProduct _model = new();
private List<EProductCategory> _productCategories = new();
private List<EBusinessUnit> _businessUnits = new();
private bool isSaving = false;
protected override async Task OnInitializedAsync()
{