diff --git a/Core/Interfaces/IAdjustmentReasonDom.cs.cs b/Core/Interfaces/IAdjustmentReasonDom.cs.cs new file mode 100644 index 0000000..67b8f68 --- /dev/null +++ b/Core/Interfaces/IAdjustmentReasonDom.cs.cs @@ -0,0 +1,14 @@ +using Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Interfaces +{ + public interface IAdjustmentReasonDom + { + Task> GetAllActiveAsync(); + } +} diff --git a/Core/Interfaces/ILookUpDom.cs b/Core/Interfaces/ILookUpDom.cs new file mode 100644 index 0000000..00a8362 --- /dev/null +++ b/Core/Interfaces/ILookUpDom.cs @@ -0,0 +1,21 @@ +using Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Interfaces +{ + public interface ILookUpDom + { + Task> CustomersListAsync(string filter, int limit = 10); + Task> InstitutionsListAsync(string filter, int limit = 10); + Task> PatientsListAsync(string filter, int limit = 10); + Task> ProfessionalsListAsync(string filter, int limit = 10); + Task> PeopleListAsync(string filter, int limit = 10); + Task> BussinessUnitsListAsync(string filter, int limit = 10); + Task> ProductsListAsync(string filter, int limit = 10); + + } +} diff --git a/Core/Interfaces/IQuoteDom.cs b/Core/Interfaces/IQuoteDom.cs index 29b08c1..82bdd76 100644 --- a/Core/Interfaces/IQuoteDom.cs +++ b/Core/Interfaces/IQuoteDom.cs @@ -5,17 +5,43 @@ namespace Models.Interfaces { public interface IQuoteDom { + #region Presupuestos Task> GetAllQuotesAsync(int page = 1, int pageSize = 50); Task GetQuoteByIdAsync(int id); Task> GetQuotesByCustomerAsync(int customerId); - Task> SearchQuotesAsync(int? customerId, - string? quoteNumber,int? professionalId, int? institutionId, - int? patientId, DateTime? issueDateFrom,DateTime? issueDateTo, - string? status, int page = 1, int pageSize = 50); - + Task> SearchQuotesAsync( + int? customerId, + string? quoteNumber, + int? professionalId, + int? institutionId, + int? patientId, + DateTime? issueDateFrom, + DateTime? issueDateTo, + string? status, + int page = 1, + int pageSize = 50); Task CreateQuoteAsync(EQuoteHeader quote, int formSeriesId); Task UpdateQuoteAsync(EQuoteHeader quote); Task DeleteQuoteAsync(int id); + #endregion + + #region Ajustes + Task> GetAdjustmentsByQuoteIdAsync(int quoteId); + Task AddAdjustmentAsync(EQuoteAdjustment adjustment); + Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment); + Task DeleteAdjustmentAsync(int adjustmentId); + #endregion + + #region Impuestos + Task> GetTaxesByQuoteIdAsync(int quoteId); + Task AddTaxAsync(EQuoteTax tax); + Task UpdateTaxAsync(EQuoteTax tax); + Task DeleteTaxAsync(int taxId); + #endregion + + #region Exportación Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams); + #endregion + } -} +} \ No newline at end of file diff --git a/Core/Services/AdjustmentReasonService.cs b/Core/Services/AdjustmentReasonService.cs new file mode 100644 index 0000000..f27c2bf --- /dev/null +++ b/Core/Services/AdjustmentReasonService.cs @@ -0,0 +1,32 @@ +using Core.Interfaces; +using Domain.Entities; +using Models.Interfaces; +using System.Reflection; + +namespace Core.Services +{ + public class AdjustmentReasonService:IAdjustmentReasonDom + { + #region Declaraciones y Constructor + private readonly IPhSAdjustmentReasonRepository _repository; + public AdjustmentReasonService(IPhSAdjustmentReasonRepository repository) + { + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + } + #endregion + #region Metodos de clase + public async Task> GetAllActiveAsync() + { + try + { + return await _repository.GetAllActiveAsync(); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + throw new Exception($"{methodName} Message: {ex.Message}", ex); + } + } + #endregion + } +} diff --git a/Core/Services/LookupService .cs b/Core/Services/LookupService .cs new file mode 100644 index 0000000..cda0caf --- /dev/null +++ b/Core/Services/LookupService .cs @@ -0,0 +1,27 @@ +using Core.Interfaces; +using Domain.Entities; +using Models.Interfaces; + +namespace Core.Services +{ + public class LookupService : ILookUpDom + { + #region Declaraciones y Constructor + private readonly IPhSLookUpRepository _repository; + public LookupService(IPhSLookUpRepository repository) + { + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + } + #endregion + #region Metodos de clases + public Task> CustomersListAsync(string filter, int limit = 10) => _repository.CustomersListAsync(filter); + public Task> InstitutionsListAsync(string filter, int limit = 10) => _repository.InstitutionsListAsync(filter); + public Task> PatientsListAsync(string filter, int limit = 10) => _repository.PatientsListAsync(filter); + public Task> ProfessionalsListAsync(string filter, int limit = 10) => _repository.ProfessionalsListAsync(filter); + public Task> PeopleListAsync(string filter, int limit = 10) => _repository.PeopleListAsync(filter); + public Task> BussinessUnitsListAsync(string filter, int limit = 10) => _repository.BussinessUnitsListAsync(filter); + public Task> ProductsListAsync(string filter, int limit = 10) => + _repository.ProductsListAsync(filter); + #endregion + } +} diff --git a/Core/Services/QuoteService.cs b/Core/Services/QuoteService.cs index 8ae0a1c..e68eda7 100644 --- a/Core/Services/QuoteService.cs +++ b/Core/Services/QuoteService.cs @@ -126,6 +126,28 @@ namespace PhronCare.Core.Services.Sales await _quoteHeaderRepository.DeleteAdjustmentAsync(adjustmentId); } #endregion + + #region Impuestos + public async Task> GetTaxesByQuoteIdAsync(int quoteId) + { + return await _quoteHeaderRepository.GetTaxesByQuoteIdAsync(quoteId); + } + + public async Task AddTaxAsync(EQuoteTax tax) + { + return await _quoteHeaderRepository.AddTaxAsync(tax); + } + + public async Task UpdateTaxAsync(EQuoteTax tax) + { + await _quoteHeaderRepository.UpdateTaxAsync(tax); + } + + public async Task DeleteTaxAsync(int taxId) + { + await _quoteHeaderRepository.DeleteTaxAsync(taxId); + } + #endregion #region Exportación public async Task ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams) @@ -172,5 +194,6 @@ namespace PhronCare.Core.Services.Sales } } #endregion + } } diff --git a/Domain/Entities/EAdjustmentReason.cs b/Domain/Entities/EAdjustmentReason.cs new file mode 100644 index 0000000..512ee77 --- /dev/null +++ b/Domain/Entities/EAdjustmentReason.cs @@ -0,0 +1,15 @@ +namespace Domain.Entities +{ + public class EAdjustmentReason + { + public string Code { get; set; } = null!; + + public string Description { get; set; } = null!; + + public bool Active { get; set; } + + public DateTime Createdat { get; set; } + + } + +} diff --git a/Domain/Entities/ELookUpItem.cs b/Domain/Entities/ELookUpItem.cs new file mode 100644 index 0000000..5020747 --- /dev/null +++ b/Domain/Entities/ELookUpItem.cs @@ -0,0 +1,8 @@ +namespace Domain.Entities +{ + public class ELookUpItem + { + public int Id { get; set; } + public string Nombre { get; set; } + } +} diff --git a/Domain/Entities/EProductLookupItem.cs b/Domain/Entities/EProductLookupItem.cs new file mode 100644 index 0000000..e1c17d1 --- /dev/null +++ b/Domain/Entities/EProductLookupItem.cs @@ -0,0 +1,13 @@ +namespace Domain.Entities +{ + public class EProductLookupItem + { + public int Id { get; set; } + public string Code { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public decimal UnitPrice { get; set; } + + // Opcional para la UI + //public string DisplayName => $"{Code} - {Description}"; + } +} diff --git a/Domain/Entities/EQuoteHeader.cs b/Domain/Entities/EQuoteHeader.cs index bbc8ab5..54870de 100644 --- a/Domain/Entities/EQuoteHeader.cs +++ b/Domain/Entities/EQuoteHeader.cs @@ -62,12 +62,17 @@ public decimal? Exchangerate { get; set; } /// - /// Total del presupuesto expresado en moneda extranjera + /// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto + /// + public decimal? Netamount { get; set; } + + /// + /// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales /// public decimal? TotalForeign { get; set; } /// - /// Total final del presupuesto expresado en moneda local, considerando ajustes o acuerdos comerciales + /// Importe total del presupuesto en moneda local, calculado incluyendo impuestos y convertido según tipo de cambio pactado /// public decimal? Total { get; set; } @@ -116,93 +121,7 @@ public virtual ICollection PhSQuoteDetails { get; set; } = new List(); public virtual ICollection PhSQuoteRoles { get; set; } = new List(); + + public virtual ICollection PhSQuoteTaxes { get; set; } = new List(); } - - //public class EQuoteHeader - //{ - // /// - // /// ID interno - // /// - // public int Id { get; set; } - - // /// - // /// Relación con Tickets - // /// - // public Guid TicketId { get; set; } - - // /// - // /// Cliente asociado - // /// - // public int CustomerId { get; set; } - - // /// - // /// Unidad de negocio - // /// - // public int BusinessunitId { get; set; } - - // /// - // /// Identificador único del vendedor - // /// - // public int PeopleId { get; set; } - - // /// - // /// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc. - // /// - // public string Status { get; set; } = null!; - - // /// - // /// Fecha de emisión - // /// - // public DateTime Issuedate { get; set; } - - // /// - // /// Fecha de aprobación - // /// - // public DateOnly? Approvaldate { get; set; } - - // /// - // /// Fecha tentativa (de cirugía por ej.) - // /// - // public DateTime? Estimateddate { get; set; } - - // /// - // /// Importe estimado total - // /// - // public decimal? Estimatedamount { get; set; } - - // /// - // /// Importe aprobado - // /// - // public decimal? Approvedamount { get; set; } - - // /// - // /// Número visible del presupuesto - // /// - // public string Quotenumber { get; set; } = null!; - - // /// - // /// Cantidad de impresiones - // /// - // public int Printcount { get; set; } - - // /// - // /// Observaciones internas - // /// - // public string? Observations { get; set; } - - // /// - // /// Fecha de creación - // /// - // public DateTime Createdat { get; set; } - - // /// - // /// Fecha de modificación - // /// - // public DateTime? Modifiedat { get; set; } - - // public virtual ICollection PhSQuoteDetails { get; set; } = new List(); - - // public virtual ICollection PhSQuoteRoles { get; set; } = new List(); - - //} } diff --git a/Domain/Entities/EQuoteTax.cs b/Domain/Entities/EQuoteTax.cs new file mode 100644 index 0000000..fd13a3e --- /dev/null +++ b/Domain/Entities/EQuoteTax.cs @@ -0,0 +1,57 @@ +namespace Domain.Entities +{ + public class EQuoteTax + { + /// + /// Clave primaria autoincremental + /// + public int Id { get; set; } + + /// + /// Referencia al presupuesto (PhS_QuoteHeaders) + /// + public int QuoteheaderId { get; set; } + + /// + /// Nombre descriptivo del impuesto + /// + public string Taxname { get; set; } = null!; + + /// + /// Código o identificador oficial del impuesto (ej. AFIP) + /// + public string? Taxcode { get; set; } + + /// + /// Base imponible del impuesto (importe gravado) + /// + public decimal TaxableAmount { get; set; } + + /// + /// Alícuota aplicada (porcentaje) + /// + public decimal Taxrate { get; set; } + + /// + /// Importe final del impuesto calculado + /// + public decimal Taxamount { get; set; } + + /// + /// Indica si el impuesto está incluido en el precio final (1 = sí) + /// + public bool IsIncludedInPrice { get; set; } + + /// + /// Fecha de creación del registro + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de modificación del registro + /// + public DateTime? ModifiedAt { get; set; } + + } + +} diff --git a/Domain/obj/Domain.csproj.nuget.g.props b/Domain/obj/Domain.csproj.nuget.g.props index 3efed38..8b4eb7e 100644 --- a/Domain/obj/Domain.csproj.nuget.g.props +++ b/Domain/obj/Domain.csproj.nuget.g.props @@ -7,7 +7,7 @@ $(UserProfile)\.nuget\packages\ C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.13.1 + 6.13.2 diff --git a/Models/Interfaces/IPhSAdjustmentReasonRepository.cs b/Models/Interfaces/IPhSAdjustmentReasonRepository.cs new file mode 100644 index 0000000..a158bcd --- /dev/null +++ b/Models/Interfaces/IPhSAdjustmentReasonRepository.cs @@ -0,0 +1,9 @@ +using Domain.Entities; + +namespace Models.Interfaces +{ + public interface IPhSAdjustmentReasonRepository + { + Task> GetAllActiveAsync(); + } +} diff --git a/Models/Interfaces/IPhSLookUpRepository.cs b/Models/Interfaces/IPhSLookUpRepository.cs new file mode 100644 index 0000000..616419d --- /dev/null +++ b/Models/Interfaces/IPhSLookUpRepository.cs @@ -0,0 +1,15 @@ +using Domain.Entities; + +namespace Models.Interfaces +{ + public interface IPhSLookUpRepository + { + Task> CustomersListAsync(string filter, int limit = 10); + Task> InstitutionsListAsync(string filter, int limit = 10); + Task> PatientsListAsync(string filter, int limit = 10); + Task> ProfessionalsListAsync(string filter, int limit = 10); + Task> PeopleListAsync(string filter, int limit = 10); + Task> BussinessUnitsListAsync(string filter, int limit = 10); + Task> ProductsListAsync(string filter, int limit = 10); + } +} \ No newline at end of file diff --git a/Models/Interfaces/IPhSQuoteHeaderRepository.cs b/Models/Interfaces/IPhSQuoteHeaderRepository.cs index ebebdbc..5821577 100644 --- a/Models/Interfaces/IPhSQuoteHeaderRepository.cs +++ b/Models/Interfaces/IPhSQuoteHeaderRepository.cs @@ -21,5 +21,24 @@ namespace Models.Interfaces Task AddAdjustmentAsync(EQuoteAdjustment adjustment); Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment); Task DeleteAdjustmentAsync(int adjustmentId); + /// + /// Obtiene todos los impuestos asociados a un presupuesto dado por su ID. + /// + Task> GetTaxesByQuoteIdAsync(int quoteId); + + /// + /// Agrega un nuevo impuesto al presupuesto correspondiente. + /// + Task AddTaxAsync(EQuoteTax tax); + + /// + /// Actualiza los datos de un impuesto existente en un presupuesto. + /// + Task UpdateTaxAsync(EQuoteTax tax); + + /// + /// Elimina un impuesto asociado a un presupuesto a partir de su ID. + /// + Task DeleteTaxAsync(int taxId); } } diff --git a/Models/Models/PhOhArcataxType.cs b/Models/Models/PhOhArcataxType.cs new file mode 100644 index 0000000..402d54f --- /dev/null +++ b/Models/Models/PhOhArcataxType.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Models.Models; + +/// +/// Tabla de referencia de tipos de impuestos según AFIP para operaciones de venta. +/// +public partial class PhOhArcataxType +{ + /// + /// Identificador único del impuesto. + /// + public int Id { get; set; } + + /// + /// Código oficial del impuesto según AFIP. + /// + public int TaxCode { get; set; } + + /// + /// Descripción del impuesto. + /// + public string Description { get; set; } = null!; + + /// + /// Indica si el impuesto está activo (1) o inactivo (0). + /// + public bool IsActive { get; set; } + + /// + /// Fecha de creación del registro. + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de última modificación del registro. + /// + public DateTime? ModifiedAt { get; set; } +} diff --git a/Models/Models/PhSQuoteHeader.cs b/Models/Models/PhSQuoteHeader.cs index 566eb3f..5f51ead 100644 --- a/Models/Models/PhSQuoteHeader.cs +++ b/Models/Models/PhSQuoteHeader.cs @@ -64,12 +64,17 @@ public partial class PhSQuoteHeader public decimal? Exchangerate { get; set; } /// - /// Total del presupuesto expresado en moneda extranjera + /// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto + /// + public decimal? Netamount { get; set; } + + /// + /// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales /// public decimal? TotalForeign { get; set; } /// - /// Total final del presupuesto expresado en moneda local, considerando ajustes o acuerdos comerciales + /// Importe total del presupuesto en moneda local, calculado incluyendo impuestos y convertido según tipo de cambio pactado /// public decimal? Total { get; set; } @@ -118,4 +123,6 @@ public partial class PhSQuoteHeader public virtual ICollection PhSQuoteDetails { get; set; } = new List(); public virtual ICollection PhSQuoteRoles { get; set; } = new List(); + + public virtual ICollection PhSQuoteTaxes { get; set; } = new List(); } diff --git a/Models/Models/PhSQuoteTaxis.cs b/Models/Models/PhSQuoteTaxis.cs new file mode 100644 index 0000000..a51a55f --- /dev/null +++ b/Models/Models/PhSQuoteTaxis.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; + +namespace Models.Models; + +public partial class PhSQuoteTaxis +{ + /// + /// Clave primaria autoincremental + /// + public int Id { get; set; } + + /// + /// Referencia al presupuesto (PhS_QuoteHeaders) + /// + public int QuoteheaderId { get; set; } + + /// + /// Nombre descriptivo del impuesto + /// + public string Taxname { get; set; } = null!; + + /// + /// Código o identificador oficial del impuesto (ej. AFIP) + /// + public string? Taxcode { get; set; } + + /// + /// Base imponible del impuesto (importe gravado) + /// + public decimal TaxableAmount { get; set; } + + /// + /// Alícuota aplicada (porcentaje) + /// + public decimal Taxrate { get; set; } + + /// + /// Importe final del impuesto calculado + /// + public decimal Taxamount { get; set; } + + /// + /// Indica si el impuesto está incluido en el precio final (1 = sí) + /// + public bool IsIncludedInPrice { get; set; } + + /// + /// Fecha de creación del registro + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de modificación del registro + /// + public DateTime? ModifiedAt { get; set; } + + public virtual PhSQuoteHeader Quoteheader { get; set; } = null!; +} diff --git a/Models/Models/PhronCareOperationsHubContext.cs b/Models/Models/PhronCareOperationsHubContext.cs index 5a97cfc..76a4c4b 100644 --- a/Models/Models/PhronCareOperationsHubContext.cs +++ b/Models/Models/PhronCareOperationsHubContext.cs @@ -17,6 +17,8 @@ public partial class PhronCareOperationsHubContext : DbContext public virtual DbSet PhOhArcadocumentTypes { get; set; } + public virtual DbSet PhOhArcataxTypes { get; set; } + public virtual DbSet PhOhTaxConditions { get; set; } public virtual DbSet PhOhTickets { get; set; } @@ -65,6 +67,8 @@ public partial class PhronCareOperationsHubContext : DbContext public virtual DbSet PhSQuoteRoles { get; set; } + public virtual DbSet PhSQuoteTaxes { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) #region VERSION DOCKER { @@ -107,6 +111,38 @@ public partial class PhronCareOperationsHubContext : DbContext .HasColumnName("letter"); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PK__PhOH_ARC__3213E83F4205B7E5"); + + entity.ToTable("PhOH_ARCATaxTypes", tb => tb.HasComment("Tabla de referencia de tipos de impuestos según AFIP para operaciones de venta.")); + + entity.Property(e => e.Id) + .HasComment("Identificador único del impuesto.") + .HasColumnName("id"); + entity.Property(e => e.CreatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("Fecha de creación del registro.") + .HasColumnType("datetime") + .HasColumnName("created_at"); + entity.Property(e => e.Description) + .HasMaxLength(100) + .IsUnicode(false) + .HasComment("Descripción del impuesto.") + .HasColumnName("description"); + entity.Property(e => e.IsActive) + .HasDefaultValue(true) + .HasComment("Indica si el impuesto está activo (1) o inactivo (0).") + .HasColumnName("is_active"); + entity.Property(e => e.ModifiedAt) + .HasComment("Fecha de última modificación del registro.") + .HasColumnType("datetime") + .HasColumnName("modified_at"); + entity.Property(e => e.TaxCode) + .HasComment("Código oficial del impuesto según AFIP.") + .HasColumnName("tax_code"); + }); + modelBuilder.Entity(entity => { entity.HasKey(e => e.Id).HasName("PK__PhOH_Tax__3213E83F26F7EAEF"); @@ -878,6 +914,10 @@ public partial class PhronCareOperationsHubContext : DbContext .HasComment("Fecha de modificación") .HasColumnType("datetime") .HasColumnName("modifiedat"); + entity.Property(e => e.Netamount) + .HasComment("Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto") + .HasColumnType("decimal(18, 2)") + .HasColumnName("netamount"); entity.Property(e => e.Observations) .HasComment("Observaciones internas") .HasColumnName("observations"); @@ -907,11 +947,11 @@ public partial class PhronCareOperationsHubContext : DbContext .HasComment("Relación con Tickets") .HasColumnName("ticket_id"); entity.Property(e => e.Total) - .HasComment("Total final del presupuesto expresado en moneda local, considerando ajustes o acuerdos comerciales") + .HasComment("Importe total del presupuesto en moneda local, calculado incluyendo impuestos y convertido según tipo de cambio pactado") .HasColumnType("decimal(18, 2)") .HasColumnName("total"); entity.Property(e => e.TotalForeign) - .HasComment("Total del presupuesto expresado en moneda extranjera") + .HasComment("Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales") .HasColumnType("decimal(18, 2)") .HasColumnName("total_foreign"); }); @@ -954,6 +994,57 @@ public partial class PhronCareOperationsHubContext : DbContext .HasConstraintName("FK_QuoteRoles_Header"); }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PK__PhS_Quot__3213E83F06626551"); + + entity.ToTable("PhS_QuoteTaxes"); + + entity.Property(e => e.Id) + .HasComment("Clave primaria autoincremental") + .HasColumnName("id"); + entity.Property(e => e.CreatedAt) + .HasDefaultValueSql("(getdate())") + .HasComment("Fecha de creación del registro") + .HasColumnType("datetime") + .HasColumnName("created_at"); + entity.Property(e => e.IsIncludedInPrice) + .HasComment("Indica si el impuesto está incluido en el precio final (1 = sí)") + .HasColumnName("is_included_in_price"); + entity.Property(e => e.ModifiedAt) + .HasComment("Fecha de modificación del registro") + .HasColumnType("datetime") + .HasColumnName("modified_at"); + entity.Property(e => e.QuoteheaderId) + .HasComment("Referencia al presupuesto (PhS_QuoteHeaders)") + .HasColumnName("quoteheader_id"); + entity.Property(e => e.TaxableAmount) + .HasComment("Base imponible del impuesto (importe gravado)") + .HasColumnType("decimal(18, 2)") + .HasColumnName("taxable_amount"); + entity.Property(e => e.Taxamount) + .HasComment("Importe final del impuesto calculado") + .HasColumnType("decimal(18, 2)") + .HasColumnName("taxamount"); + entity.Property(e => e.Taxcode) + .HasMaxLength(50) + .HasComment("Código o identificador oficial del impuesto (ej. AFIP)") + .HasColumnName("taxcode"); + entity.Property(e => e.Taxname) + .HasMaxLength(100) + .HasComment("Nombre descriptivo del impuesto") + .HasColumnName("taxname"); + entity.Property(e => e.Taxrate) + .HasComment("Alícuota aplicada (porcentaje)") + .HasColumnType("decimal(5, 2)") + .HasColumnName("taxrate"); + + entity.HasOne(d => d.Quoteheader).WithMany(p => p.PhSQuoteTaxes) + .HasForeignKey(d => d.QuoteheaderId) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_PhS_QuoteTaxes_QuoteHeaders"); + }); + OnModelCreatingPartial(modelBuilder); } diff --git a/Models/Repositories/PhSAdjustmentReasonRepository.cs b/Models/Repositories/PhSAdjustmentReasonRepository.cs new file mode 100644 index 0000000..faa28c6 --- /dev/null +++ b/Models/Repositories/PhSAdjustmentReasonRepository.cs @@ -0,0 +1,25 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace Models.Repositories +{ + public class PhSAdjustmentReasonRepository(PhronCareOperationsHubContext context) : IPhSAdjustmentReasonRepository + { + #region Declaraciones y Constructor + private readonly PhronCareOperationsHubContext _context = context; + #endregion + #region Metodos de clase + public async Task> GetAllActiveAsync() + { + var adjustement= await _context.PhSAdjustmentReasons + .Where(x => x.Active) + .OrderBy(x => x.Code) + .ToListAsync(); + return adjustement.Select(EntityMapper.MapEntity); + } + #endregion + } +} diff --git a/Models/Repositories/PhSLookUpRepository.cs b/Models/Repositories/PhSLookUpRepository.cs new file mode 100644 index 0000000..44466ae --- /dev/null +++ b/Models/Repositories/PhSLookUpRepository.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using Domain.Entities; +using Models.Interfaces; +using Models.Models; +using System.Globalization; + +namespace Models.Repositories +{ + public class PhSLookUpRepository(PhronCareOperationsHubContext context) : IPhSLookUpRepository +{ + #region Declaraciones + private readonly PhronCareOperationsHubContext _context = context; + #endregion + + public async Task> CustomersListAsync(string filter, int limit = 10) + => await _context.PhSCustomers + .Where(c => c.Name.Contains(filter)) + .OrderBy(c => c.Name) + .Select(c => new ELookUpItem { Id = c.Id, Nombre = c.Name }) + .Take(limit) + .ToListAsync(); + + public async Task> InstitutionsListAsync(string filter, int limit = 10) + => await _context.PhSInstitutions + .Where(c => c.Name.Contains(filter)) + .OrderBy(c => c.Name) + .Select(c => new ELookUpItem { Id = c.Id, Nombre = c.Name }) + .Take(limit) + .ToListAsync(); + public async Task> PatientsListAsync(string filter, int limit = 10) + { + var ti = CultureInfo.CurrentCulture.TextInfo; + + return await _context.PhSPatients + .Where(c => c.Firstname.Contains(filter) || c.Lastname.Contains(filter)) + .OrderBy(c => c.Firstname) + .Select(c => new ELookUpItem + { + Id = c.Id, + Nombre = ti.ToTitleCase(c.Firstname.ToLower()) + " " + ti.ToTitleCase(c.Lastname.ToLower()) + }) + .Take(limit) + .ToListAsync(); + } + public async Task> ProfessionalsListAsync(string filter, int limit = 10) + { + TextInfo ti = CultureInfo.CurrentCulture.TextInfo; + + return await _context.PhSProfessionals + .Where(c => c.Fullname.Contains(filter)) + .OrderBy(c => c.Fullname) + .Select(c => new ELookUpItem + { + Id = c.Id, + Nombre = ti.ToTitleCase(c.Fullname.ToLower()) + }) + .Take(limit) + .ToListAsync(); + } + public async Task> PeopleListAsync(string filter, int limit = 10) + => await _context.PhSPeople + .Where(c => c.Name.Contains(filter)) + .OrderBy(c => c.Name) + .Select(c => new ELookUpItem { Id = c.Id, Nombre = c.Name }) + .Take(limit) + .ToListAsync(); + public async Task> BussinessUnitsListAsync(string filter, int limit = 10) + => await _context.PhSBusinessUnits + .Where(c => c.Code.Contains(filter) || c.Description.Contains(filter)) + .OrderBy(c => c.Code) + .Select(c => new ELookUpItem { Id = c.Id, Nombre = c.Code + " | " + c.Description }) + .Take(limit) + .ToListAsync(); + public async Task> ProductsListAsync(string filter, int limit = 10) + => await _context.PhSProducts + .Where(c => c.Name.Contains(filter) || c.Description.Contains(filter)) + .OrderBy(c => c.Name) + .Select(c => new EProductLookupItem { Id = c.Id, Code=c.Businessunits.Code, Description = c.Description , UnitPrice= c.Baseprice }) + .Take(limit) + .ToListAsync(); + + } +} diff --git a/Models/Repositories/PhSQuoteHeaderRepository.cs b/Models/Repositories/PhSQuoteHeaderRepository.cs index f90f6d1..c7f8677 100644 --- a/Models/Repositories/PhSQuoteHeaderRepository.cs +++ b/Models/Repositories/PhSQuoteHeaderRepository.cs @@ -16,6 +16,7 @@ namespace PhronCare.Core.Data.Repositories.Sales .Include(q => q.PhSQuoteDetails) .Include(q => q.PhSQuoteRoles) .Include(q => q.PhSQuoteAdjustments) + .Include(q => q.PhSQuoteTaxes) .AsNoTracking(); var pagedEntities = await query.ToPagedResultAsync(page, pageSize); @@ -34,6 +35,7 @@ namespace PhronCare.Core.Data.Repositories.Sales .Include(q => q.PhSQuoteDetails) .Include(q => q.PhSQuoteRoles) .Include(q => q.PhSQuoteAdjustments) + .Include(q => q.PhSQuoteTaxes) .FirstOrDefaultAsync(q => q.Id == id); return entity != null ? EntityMapper.MapEntity(entity) : null; @@ -45,6 +47,7 @@ namespace PhronCare.Core.Data.Repositories.Sales .Include(q => q.PhSQuoteDetails) .Include(q => q.PhSQuoteRoles) .Include(q => q.PhSQuoteAdjustments) + .Include(q => q.PhSQuoteTaxes) .ToListAsync(); return entities.Select(EntityMapper.MapEntity); @@ -58,6 +61,7 @@ namespace PhronCare.Core.Data.Repositories.Sales .Include(q => q.PhSQuoteDetails) .Include(q => q.PhSQuoteRoles) .Include(q => q.PhSQuoteAdjustments) + .Include(q => q.PhSQuoteTaxes) .AsQueryable(); if (customerId.HasValue) @@ -150,5 +154,55 @@ namespace PhronCare.Core.Data.Repositories.Sales await _context.SaveChangesAsync(); } } + + // ---------------------------- + // Métodos para Impuestos + // ---------------------------- + + /// + /// Obtiene todos los impuestos asociados a un presupuesto dado por su ID. + /// + public async Task> GetTaxesByQuoteIdAsync(int quoteId) + { + var taxes = await _context.PhSQuoteTaxes + .Where(t => t.QuoteheaderId == quoteId) + .ToListAsync(); + + return taxes.Select(EntityMapper.MapEntity); + } + + /// + /// Agrega un nuevo impuesto al presupuesto correspondiente. + /// + public async Task AddTaxAsync(EQuoteTax tax) + { + var dbEntity = EntityMapper.MapEntity(tax); + _context.PhSQuoteTaxes.Add(dbEntity); + await _context.SaveChangesAsync(); + return EntityMapper.MapEntity(dbEntity); + } + + /// + /// Actualiza los datos de un impuesto existente en un presupuesto. + /// + public async Task UpdateTaxAsync(EQuoteTax tax) + { + var dbEntity = EntityMapper.MapEntity(tax); + _context.PhSQuoteTaxes.Update(dbEntity); + await _context.SaveChangesAsync(); + } + + /// + /// Elimina un impuesto asociado a un presupuesto a partir de su ID. + /// + public async Task DeleteTaxAsync(int taxId) + { + var entity = await _context.PhSQuoteTaxes.FindAsync(taxId); + if (entity != null) + { + _context.PhSQuoteTaxes.Remove(entity); + await _context.SaveChangesAsync(); + } + } } } \ No newline at end of file diff --git a/Models/obj/Models.csproj.nuget.g.props b/Models/obj/Models.csproj.nuget.g.props index d21a6b0..57bd42c 100644 --- a/Models/obj/Models.csproj.nuget.g.props +++ b/Models/obj/Models.csproj.nuget.g.props @@ -7,7 +7,7 @@ $(UserProfile)\.nuget\packages\ C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.13.1 + 6.13.2 diff --git a/phronCare.API/Controllers/Sales/AdjustmentReasonController.cs b/phronCare.API/Controllers/Sales/AdjustmentReasonController.cs new file mode 100644 index 0000000..06e9684 --- /dev/null +++ b/phronCare.API/Controllers/Sales/AdjustmentReasonController.cs @@ -0,0 +1,30 @@ +using Core.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace phronCare.API.Controllers.Sales +{ + [Route("api/[controller]")] + [ApiController] + public class AdjustmentReasonController : ControllerBase + { + private readonly IAdjustmentReasonDom _adjustmentReasonDom; + public AdjustmentReasonController(IAdjustmentReasonDom adjustmentReasonService) + { + _adjustmentReasonDom = adjustmentReasonService ?? throw new ArgumentNullException(nameof(adjustmentReasonService)); + } + [HttpGet("GetAll")] + public async Task GetAll() + { + try + { + var result = await _adjustmentReasonDom.GetAllActiveAsync(); + return Ok(result); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + } +} diff --git a/phronCare.API/Controllers/Sales/LookUpController.cs b/phronCare.API/Controllers/Sales/LookUpController.cs new file mode 100644 index 0000000..25c71e5 --- /dev/null +++ b/phronCare.API/Controllers/Sales/LookUpController.cs @@ -0,0 +1,38 @@ +using Core.Interfaces; +using Domain.Entities; +using Microsoft.AspNetCore.Mvc; + +namespace phronCare.API.Controllers.Sales +{ + [Route("api/[controller]")] + [ApiController] + public class LookUpController : ControllerBase + { + private readonly ILookUpDom _lookup; + public LookUpController(ILookUpDom lookup) => _lookup = lookup; + + [HttpGet("customers")] + public Task> Customers([FromQuery] string q) + => _lookup.CustomersListAsync(q); + [HttpGet("institutions")] + public Task> Institutions([FromQuery] string q) + => _lookup.InstitutionsListAsync(q); + [HttpGet("patients")] + public Task> Patients([FromQuery] string q) + => _lookup.PatientsListAsync(q); + [HttpGet("people")] + public Task> People([FromQuery] string q) + => _lookup.PeopleListAsync(q); + [HttpGet("professionals")] + public Task> Professionals([FromQuery] string q) + => _lookup.ProfessionalsListAsync(q); + [HttpGet("bussinessunits")] + public Task> BussinessUnits([FromQuery] string q) + => _lookup.BussinessUnitsListAsync(q); + + [HttpGet("products")] + public Task> Products([FromQuery] string q) + => _lookup.ProductsListAsync(q); + + } +} diff --git a/phronCare.API/Controllers/Sales/QuoteController.cs b/phronCare.API/Controllers/Sales/QuoteController.cs index 47d59c2..8a80d0a 100644 --- a/phronCare.API/Controllers/Sales/QuoteController.cs +++ b/phronCare.API/Controllers/Sales/QuoteController.cs @@ -1,5 +1,4 @@ -using Core.Interfaces; -using Domain.Entities; +using Domain.Entities; using Domain.Generics; using Microsoft.AspNetCore.Mvc; using Models.Interfaces; @@ -12,12 +11,13 @@ namespace phronCare.API.Controllers.Sales public class QuoteController : ControllerBase { private readonly IQuoteDom _quoteService; - public QuoteController(IQuoteDom quoteService) { _quoteService = quoteService ?? throw new ArgumentNullException(nameof(quoteService)); } + #region Obtener Presupuestos + [HttpGet("all")] public async Task GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50) { @@ -87,6 +87,10 @@ namespace phronCare.API.Controllers.Sales } } + #endregion + + #region Crear / Actualizar / Eliminar + [HttpPost("create")] public async Task Create([FromBody] EQuoteHeader quote, [FromQuery] int formSeriesId) { @@ -145,6 +149,11 @@ namespace phronCare.API.Controllers.Sales return StatusCode(500, $"{methodName} Message: {ex.Message}"); } } + + #endregion + + #region Exportación + [HttpPost("exportfiltered")] public async Task ExportFiltered([FromBody] QuoteSearchParams searchParams) { @@ -161,5 +170,76 @@ namespace phronCare.API.Controllers.Sales } } + #endregion + + #region Impuestos (QuoteTaxes) + + [HttpGet("{quoteId:int}/taxes")] + public async Task GetTaxes(int quoteId) + { + try + { + var taxes = await _quoteService.GetTaxesByQuoteIdAsync(quoteId); + return Ok(taxes); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpPost("{quoteId:int}/taxes")] + public async Task AddTax(int quoteId, [FromBody] EQuoteTax tax) + { + try + { + if (tax == null || quoteId != tax.QuoteheaderId) + return BadRequest("Datos inválidos para el impuesto."); + + var result = await _quoteService.AddTaxAsync(tax); + return Ok(result); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpPut("taxes")] + public async Task UpdateTax([FromBody] EQuoteTax tax) + { + try + { + if (tax == null || tax.Id <= 0) + return BadRequest("Datos inválidos para actualizar el impuesto."); + + await _quoteService.UpdateTaxAsync(tax); + return Ok("Impuesto actualizado correctamente."); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpDelete("taxes/{taxId:int}")] + public async Task DeleteTax(int taxId) + { + try + { + await _quoteService.DeleteTaxAsync(taxId); + return Ok("Impuesto eliminado correctamente."); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + #endregion } } diff --git a/phronCare.API/Program.cs b/phronCare.API/Program.cs index 347b483..bcc9610 100644 --- a/phronCare.API/Program.cs +++ b/phronCare.API/Program.cs @@ -237,10 +237,18 @@ static void RepositorysAndServices(WebApplicationBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + + // Registrar el service de lookup + builder.Services.AddScoped(); + builder.Services.AddScoped(); + } \ No newline at end of file diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json index 91ba882..17c91bc 100644 --- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json +++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json @@ -167,6 +167,16 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.AdjustmentReasonController", + "Method": "GetAll", + "RelativePath": "api/AdjustmentReason/GetAll", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.AuthenticationController", "Method": "ConfirmEmail", @@ -702,6 +712,188 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "BussinessUnits", + "RelativePath": "api/LookUp/bussinessunits", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.ELookUpItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "Customers", + "RelativePath": "api/LookUp/customers", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.ELookUpItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "Institutions", + "RelativePath": "api/LookUp/institutions", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.ELookUpItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "Patients", + "RelativePath": "api/LookUp/patients", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.ELookUpItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "People", + "RelativePath": "api/LookUp/people", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.ELookUpItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "Products", + "RelativePath": "api/LookUp/products", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.EProductLookupItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.LookUpController", + "Method": "Professionals", + "RelativePath": "api/LookUp/professionals", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "q", + "Type": "System.String", + "IsRequired": false + } + ], + "ReturnTypes": [ + { + "Type": "System.Collections.Generic.IEnumerable\u00601[[Domain.Entities.ELookUpItem, Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", + "MediaTypes": [ + "text/plain", + "application/json", + "text/json" + ], + "StatusCode": 200 + } + ] + }, { "ContainingType": "phronCare.API.Controllers.Sales.PatientController", "Method": "GetById", @@ -1364,6 +1556,43 @@ } ] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "GetTaxes", + "RelativePath": "api/Quote/{quoteId}/taxes", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "quoteId", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "AddTax", + "RelativePath": "api/Quote/{quoteId}/taxes", + "HttpMethod": "POST", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "quoteId", + "Type": "System.Int32", + "IsRequired": true + }, + { + "Name": "tax", + "Type": "Domain.Entities.EQuoteTax", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "Method": "GetAll", @@ -1499,6 +1728,38 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "UpdateTax", + "RelativePath": "api/Quote/taxes", + "HttpMethod": "PUT", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "tax", + "Type": "Domain.Entities.EQuoteTax", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "DeleteTax", + "RelativePath": "api/Quote/taxes/{taxId}", + "HttpMethod": "DELETE", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "taxId", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "Method": "Update", diff --git a/phronCare.UIBlazor/Pages/Sales/Modals/ProductSelectorModal.razor b/phronCare.UIBlazor/Pages/Sales/Modals/ProductSelectorModal.razor new file mode 100644 index 0000000..1262fd6 --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Modals/ProductSelectorModal.razor @@ -0,0 +1,108 @@ +@using Blazored.Modal +@using Blazored.Modal.Services +@using System.Globalization + +@inject Services.Lookups.ISalesLookupService SalesLookupService + +
+
+
+ Buscar producto +
+
+ + + + @if (_products is null) + { +

Escriba al menos 3 caracteres para buscar.

+ } + else if (!_products.Any()) + { +

No se encontraron productos.

+ } + else + { +
+ + + + + + + + + + + @foreach (var product in _products) + { + + + + + + + } + +
CódigoDescripciónPrecio
@product.Code@product.Description$ @product.UnitPrice.ToString("N2") + +
+
+ } + +
+ +
+
+ +@code { + [CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!; + + private List? _products; + + private string _searchTermBacking = string.Empty; + private string _searchTerm + { + get => _searchTermBacking; + set + { + if (value != _searchTermBacking) + { + _searchTermBacking = value; + _ = Buscar(); + } + } + } + + private async Task OnInputChanged(ChangeEventArgs e) + { + _searchTerm = e.Value?.ToString() ?? string.Empty; + await Buscar(); + } + + private async Task Buscar() + { + if (_searchTerm.Length < 3) + { + _products = null; + return; + } + + _products = (await SalesLookupService.SearchProductsAsync(_searchTerm)).ToList(); + StateHasChanged(); + } + + private async Task SelectProduct(EProductLookupItem item) + { + await ModalInstance.CloseAsync(ModalResult.Ok(item)); + } + + private async Task Cancelar() + { + await ModalInstance.CancelAsync(); + } +} diff --git a/phronCare.UIBlazor/Pages/Sales/Modals/ProfessionalQuickAddModal.razor b/phronCare.UIBlazor/Pages/Sales/Modals/ProfessionalQuickAddModal.razor new file mode 100644 index 0000000..56bfe6c --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Modals/ProfessionalQuickAddModal.razor @@ -0,0 +1,142 @@ +@using phronCare.UIBlazor.Services.Sales + + @inject ProfessionalSpecialtyService specialtyService + @inject DocumentTypeService documentTypeService + +@inject ProfessionalService ProfessionalService +@inject IToastService Toast +@inject Blazored.Modal.Services.IModalService ModalService + + + + + + +
+
+
Nuevo Profesional
+
+
+
+
+ + + +
+
+
+
+@* + + + + + + + *@ + + + + @foreach (var type in documentTypes) + { + + } + + + +
+
+ + + +
+
+ + +
+
+
+
+ + + @foreach (var option in professionalTypes) + { + + } + + +
+
+ + + + @foreach (var s in specialties) + { + + } + + +
+ +
+
+ +
+
+ +@code { + [CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!; + private List documentTypes = new(); + private List specialties = new(); + + + private EProfessional _model = new(); + + protected override async Task OnInitializedAsync() + { + await LoadSpecialties(); + await LoadDocumentTypes(); + } + private List<(string Value, string Text)> professionalTypes = new() + { + ("","--- Seleccionar ---"), + ("Medico", "Médico"), + ("Instrumentador", "Instrumentador quirúrgico"), + ("Enfermero", "Enfermero/a"), + ("Tecnico", "Técnico quirúrgico") + }; + private async Task LoadDocumentTypes() + { + documentTypes = await documentTypeService.GetAllAsync(); + } + + private async Task LoadSpecialties() + { + specialties = await specialtyService.GetAllAsync(); + } + + private async Task HandleValidSubmit() + { + var result = await ProfessionalService.CreateAsync(_model); + + if (result.IsSuccessStatusCode) + { + Toast.ShowSuccess("Profesional creado."); + var newItem = new ELookUpItem { Id = _model.Id, Nombre = _model.Fullname }; + ModalInstance.CloseAsync(ModalResult.Ok(newItem)); + } + else + { + var error = await result.Content.ReadAsStringAsync(); + Toast.ShowError($"Error: {error}"); + } + } + + private async Task Cancelar() + { + ModalInstance.CancelAsync(); + } +} diff --git a/phronCare.UIBlazor/Pages/Sales/Modals/QuoteAdjustmentQuickAddModal.razor b/phronCare.UIBlazor/Pages/Sales/Modals/QuoteAdjustmentQuickAddModal.razor new file mode 100644 index 0000000..7f7e80e --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Modals/QuoteAdjustmentQuickAddModal.razor @@ -0,0 +1,75 @@ +@using Blazored.Modal +@using Blazored.Modal.Services +@using Domain.Entities +@inject IToastService Toast +@inject Services.Lookups.ISalesLookupService SalesLookupService + + + + + +
+
+
Agregar Ajuste
+
+ +
+
+ + + + @foreach (var reason in _adjustmentReasons) + { + + } + + +
+ +
+ + + +
+
+ + +
+
+ +@code { + [CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!; + + private QuoteAdjustmentDto _model = new(); + private List _adjustmentReasons = new(); + + protected override async Task OnInitializedAsync() + { + _adjustmentReasons = (await SalesLookupService.GetAdjustmentReasonsAsync()).ToList(); + } + + private async Task HandleValidSubmit() + { + if (string.IsNullOrWhiteSpace(_model.ReasonCode) || _model.Amount <= 0) + { + Toast.ShowError("Debe seleccionar un motivo y un monto válido."); + return; + } + + await ModalInstance.CloseAsync(ModalResult.Ok(_model)); + } + + private async Task Cancelar() + { + await ModalInstance.CancelAsync(); + } + + public class QuoteAdjustmentDto + { + public string ReasonCode { get; set; } = ""; + public decimal Amount { get; set; } + } +} diff --git a/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor new file mode 100644 index 0000000..d1f8e3b --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor @@ -0,0 +1,373 @@ +@page "/quote/create" +@using System.Globalization; +@using System.Net.Http.Json +@using Blazored.Typeahead +@using Services.Lookups +@using phronCare.UIBlazor.Pages.Sales.Modals +@inject ISalesLookupService SalesLookupService +@inject IModalService Modal + + +
+
+
+

Emisión de Presupuesto

+
+
+ +
+
+ + + @customer.Nombre + @customer.Nombre + + +
+
+ + + @item.Nombre + @item.Nombre + + +
+
+ +
+
+ + + @item.Nombre + @item.Nombre + + + + +
+
+ + + @item.Nombre + @item.Nombre + + +
+
+ +
+
+ + + @item.Nombre + @item.Nombre + + +
+
+ + + + @foreach (var unidad in _businessUnits) + { + + } + +
+
+ +
+
+ + + + + +
+
+ + +
+
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
Productos Cotizados
+ + + +
+
+ + + + + + + + + + + + + @if (_quoteModel.PhSQuoteDetails.Any()) + { + foreach (var item in _quoteModel.PhSQuoteDetails) + { + + + + + + + + + + } + } + else + { + + + + } + +
ProductoDescripciónCantidadPrecio UnitarioTotal
@item.ProductId + + + + + + $ @($"{item.Quantity * item.Unitprice:0.00}") + +
No hay productos agregados.
+
+ + +
+
+ + + @if (_quoteModel.PhSQuoteAdjustments.Any()) + { +
+ @foreach (var adj in _quoteModel.PhSQuoteAdjustments) + { + + @adj.ReasonCode + + + } +
+ } + else + { +

Sin ajustes.

+ } + + +
+
+
+
+
    +
  • + Subtotal + $ @_netAmount.ToString("N2") +
  • +
  • + Taxes + $ @_taxAmount.ToString("N2") +
  • +
  • + Total + $ @_grandTotal.ToString("N2") +
  • +
+
+
+ +
+ +
+
+
+ +@code { + private EQuoteHeader _quoteModel = new(); + private ELookUpItem? _selectedCustomer; + private ELookUpItem? _selectedProfessional; + private ELookUpItem? _selectedInstitution; + private ELookUpItem? _selectedPatient; + private ELookUpItem? _selectedPerson; + private List _businessUnits = new(); + private decimal _netAmount = 0; + private decimal _taxAmount = 0; + private decimal _grandTotal = 0; + + private Task OnValueChanged(EQuoteDetail item, T value, Action setter) + { + setter(item, value); + RecalculateTotals(); + return Task.CompletedTask; + } + + protected override async Task OnInitializedAsync() + { + _businessUnits = (await SalesLookupService.SearchBussinessUnitsAsync(string.Empty)).ToList(); + } + private async Task AddNewProduct() + { + var options = new ModalOptions() + { + Size = ModalSize.Large, + HideHeader = true + }; + var modal = Modal.Show("", options); + var result = await modal.Result; + if (!result.Cancelled && result.Data is EProductLookupItem selected) + { + var newDetail = new EQuoteDetail + { + ProductId = selected.Id, + ProductDescription = selected.Description, + Quantity = 1, + Unitprice = selected.UnitPrice, + Approved = false, + Createdat = DateTime.Now + }; + _quoteModel.PhSQuoteDetails.Add(newDetail); + } + } + private async Task AddNewProfessional() + { + var options = new ModalOptions() + { + Size = ModalSize.Large, + HideHeader = true + }; + var modal = Modal.Show(options); + var result = await modal.Result; + if (!result.Cancelled && result.Data is ELookUpItem nuevo) + { + _selectedProfessional = nuevo; + // No hay ProfessionalId en el modelo base, pero si lo usás, actualizalo aquí. + // _quoteModel.ProfessionalId = nuevo.Id; + } + } + private Task OnCustomerSelected(ELookUpItem item) + => SetLookupSelection(item, sel => _selectedCustomer = sel, id => _quoteModel.CustomerId = id); + private Task OnPersonSelected(ELookUpItem item) + => SetLookupSelection(item, sel => _selectedPerson = sel, id => _quoteModel.PeopleId = id); + private Task OnProfessionalSelected(ELookUpItem item) + => SetLookupSelection(item, sel => _selectedProfessional = sel); + private Task OnInstitutionSelected(ELookUpItem item) + => SetLookupSelection(item, sel => _selectedInstitution = sel); + private Task OnPatientSelected(ELookUpItem item) + => SetLookupSelection(item, sel => _selectedPatient = sel); + private Task SetLookupSelection(ELookUpItem? item, Action setSelected, Action? setModelId = null) + { + setSelected(item); + if (item != null && setModelId != null) setModelId(item.Id); + + return Task.CompletedTask; + } + private void OnDetailChanged(EQuoteDetail item) { } + private void RemoveDetail(EQuoteDetail item) + => _quoteModel.PhSQuoteDetails.Remove(item); + private void RecalculateTotals() + { + _netAmount = _quoteModel.PhSQuoteDetails.Sum(d => d.Quantity * d.Unitprice); + _taxAmount = _quoteModel.PhSQuoteTaxes.Sum(t => t.Taxamount); + _grandTotal = _netAmount + _taxAmount; + _quoteModel.Netamount = _netAmount; + _quoteModel.Total = _grandTotal; + } + + private async Task OpenAdjustmentModal() + { + var modal = Modal.Show("Agregar Ajuste", new ModalOptions { HideHeader = true }); + var result = await modal.Result; + + if (!result.Cancelled && result.Data is QuoteAdjustmentQuickAddModal.QuoteAdjustmentDto dto) + { + _quoteModel.PhSQuoteAdjustments.Add(new EQuoteAdjustment + { + ReasonCode = dto.ReasonCode, + Amount = dto.Amount + }); + } + } + + private void RemoveAdjustment(EQuoteAdjustment adj) + { + _quoteModel.PhSQuoteAdjustments.Remove(adj); + } + +} diff --git a/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor.css b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor.css new file mode 100644 index 0000000..978f176 --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor.css @@ -0,0 +1,56 @@ +.add-wrapper { + position: relative; +} + +.add-wrapper .add-btn { + display: flex; + opacity:0; + position: absolute; + top: 0.25rem; + right: 0.25rem; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + align-items: center; + justify-content: center; + background-color: var(--bs-success); /* Bootstrap success */ + color: white; + border: none; + cursor: pointer; + font-size: 1rem; + line-height: 1; + transition: opacity 0.4s ease-in-out; +} + +.add-wrapper:hover .add-btn { + opacity: 1; + pointer-events: auto; +} +.adjustment-tag { + background-color: #408044; /* Verde fuerte */ + color: #dfffe5; /* Verde claro para texto */ + border-radius: 12px; + padding: 0.3rem 0.75rem; + display: inline-flex; + align-items: center; + font-weight: 600; + font-size: 0.875rem; + margin-right: 0.5rem; + margin-bottom: 0.5rem; + cursor: default; /* Usa flecha normal del mouse */ + user-select: none; /* No permite seleccionar texto */ +} +.adjustment-tag .remove-btn { + background: none; + border: none; + color: #dfffe5; + margin-left: 0.5rem; + cursor: pointer; + font-size: 1rem; + line-height: 1; + padding: 0; +} +.adjustment-tag .remove-btn:hover { + color: #dfffe5; /* Mismo tono claro del texto */ +} + diff --git a/phronCare.UIBlazor/Program.cs b/phronCare.UIBlazor/Program.cs index 5c483ba..3274ca4 100644 --- a/phronCare.UIBlazor/Program.cs +++ b/phronCare.UIBlazor/Program.cs @@ -1,13 +1,15 @@ -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.AspNetCore.Components.Web; -using phronCare.UIBlazor.Services.Authorization; using phronCare.UIBlazor; using phronCare.UIBlazor.Services.UI; +using phronCare.UIBlazor.Services.Sales; +using phronCare.UIBlazor.Services.Lookups; +using phronCare.UIBlazor.Services.Tickets; +using phronCare.UIBlazor.Services.Authorization; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + using Blazored.Modal; using Blazored.Toast; -using phronCare.UIBlazor.Services.Tickets; -using phronCare.UIBlazor.Services.Sales; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -37,19 +39,7 @@ if (config != null) } #endregion #region Injection Dependencis -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); +InjectDependencies(builder); #endregion #region UI builder.Services.AddBlazoredModal(); @@ -58,3 +48,22 @@ builder.Services.AddSingleton(); #endregion await builder.Build().RunAsync(); + +static void InjectDependencies(WebAssemblyHostBuilder builder) +{ + builder.Services.AddScoped(); + + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); +} \ No newline at end of file diff --git a/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs b/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs new file mode 100644 index 0000000..39dfab6 --- /dev/null +++ b/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs @@ -0,0 +1,16 @@ +using Domain.Entities; + +namespace phronCare.UIBlazor.Services.Lookups +{ + public interface ISalesLookupService + { + Task> SearchCustomersAsync(string filtro); + Task> SearchInstitutionsAsync(string filtro); + Task> SearchPatientsAsync(string filtro); + Task> SearchPeopleAsync(string filtro); + Task> SearchProfessionalsAsync(string filtro); + Task> SearchBussinessUnitsAsync(string filtro); + Task> SearchProductsAsync(string filtro); + Task> GetAdjustmentReasonsAsync(); + } +} diff --git a/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs b/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs new file mode 100644 index 0000000..f9facef --- /dev/null +++ b/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs @@ -0,0 +1,53 @@ +using Domain.Entities; +using System.Net.Http.Json; + +namespace phronCare.UIBlazor.Services.Lookups +{ + public class SalesLookupService : ISalesLookupService + { + private readonly HttpClient _http; + public SalesLookupService(HttpClient http) => _http = http; + public Task> SearchCustomersAsync(string filtro) + => FetchAsync($"api/lookup/customers?q={Uri.EscapeDataString(filtro)}", filtro); + public Task> SearchProfessionalsAsync(string filtro) + => FetchAsync($"api/lookup/professionals?q={Uri.EscapeDataString(filtro)}", filtro); + public Task> SearchInstitutionsAsync(string filtro) + => FetchAsync($"api/lookup/institutions?q={Uri.EscapeDataString(filtro)}", filtro); + public Task> SearchPatientsAsync(string filtro) + => FetchAsync($"api/lookup/patients?q={Uri.EscapeDataString(filtro)}", filtro); + public Task> SearchPeopleAsync(string filtro) + => FetchAsync($"api/lookup/people?q={Uri.EscapeDataString(filtro)}", filtro); + public async Task> SearchBussinessUnitsAsync(string filtro) + { + var result = await _http.GetFromJsonAsync>("api/businessunit/all"); + + return result?.Select(b => new ELookUpItem + { + Id = b.Id, + Nombre = $"{b.Code} | {b.Description}" + }) ?? Enumerable.Empty(); + } + public async Task> FetchAsync(string url,string filtro) + { + if (string.IsNullOrWhiteSpace(url) || filtro.Length < 3) + return Array.Empty(); + + var items = await _http.GetFromJsonAsync(url); + return items ?? Array.Empty(); + } + + public Task> SearchProductsAsync(string filtro) + => FetchProductsAsync($"api/lookup/products?q={Uri.EscapeDataString(filtro)}"); + public async Task> GetAdjustmentReasonsAsync() + { + var items = await _http.GetFromJsonAsync("api/adjustmentreason/getall"); + return items ?? Array.Empty(); + } + + private async Task> FetchProductsAsync(string url) + { + var items = await _http.GetFromJsonAsync(url); + return items ?? Array.Empty(); + } + } +} diff --git a/phronCare.UIBlazor/Shared/Components/PhLoginState.razor b/phronCare.UIBlazor/Shared/Components/PhLoginState.razor index 462e748..e2badcb 100644 --- a/phronCare.UIBlazor/Shared/Components/PhLoginState.razor +++ b/phronCare.UIBlazor/Shared/Components/PhLoginState.razor @@ -2,7 +2,7 @@ + + } diff --git a/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.dgspec.json b/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.dgspec.json index 47e6957..61d07fc 100644 --- a/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.dgspec.json +++ b/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.dgspec.json @@ -130,6 +130,10 @@ "target": "Package", "version": "[4.2.1, )" }, + "Blazored.Typeahead": { + "target": "Package", + "version": "[4.7.0, )" + }, "Microsoft.AspNetCore.Components.Authorization": { "target": "Package", "version": "[8.0.6, )" diff --git a/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.g.props b/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.g.props index a146884..5f2574f 100644 --- a/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.g.props +++ b/phronCare.UIBlazor/obj/phronCare.UIBlazor.csproj.nuget.g.props @@ -17,6 +17,7 @@ + diff --git a/phronCare.UIBlazor/obj/project.assets.json b/phronCare.UIBlazor/obj/project.assets.json index 4b3bfed..bba5d6a 100644 --- a/phronCare.UIBlazor/obj/project.assets.json +++ b/phronCare.UIBlazor/obj/project.assets.json @@ -43,6 +43,25 @@ "buildMultiTargeting/Blazored.Toast.props": {} } }, + "Blazored.Typeahead/4.7.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Components": "6.0.3", + "Microsoft.AspNetCore.Components.Web": "6.0.3" + }, + "compile": { + "lib/net6.0/Blazored.Typeahead.dll": {} + }, + "runtime": { + "lib/net6.0/Blazored.Typeahead.dll": {} + }, + "build": { + "buildTransitive/Blazored.Typeahead.props": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/Blazored.Typeahead.props": {} + } + }, "EPPlus/7.5.2": { "type": "package", "dependencies": { @@ -728,6 +747,25 @@ "buildMultiTargeting/Blazored.Toast.props": {} } }, + "Blazored.Typeahead/4.7.0": { + "type": "package", + "dependencies": { + "Microsoft.AspNetCore.Components": "6.0.3", + "Microsoft.AspNetCore.Components.Web": "6.0.3" + }, + "compile": { + "lib/net6.0/Blazored.Typeahead.dll": {} + }, + "runtime": { + "lib/net6.0/Blazored.Typeahead.dll": {} + }, + "build": { + "buildTransitive/Blazored.Typeahead.props": {} + }, + "buildMultiTargeting": { + "buildMultiTargeting/Blazored.Typeahead.props": {} + } + }, "EPPlus/7.5.2": { "type": "package", "dependencies": { @@ -1396,6 +1434,25 @@ "staticwebassets/Blazored.Toast.bundle.scp.css" ] }, + "Blazored.Typeahead/4.7.0": { + "sha512": "fTN4Bt9rwEE/d33FXFd+h/DBjVtsFJLRf9B8mu0zJWMWG5Mk2tU2il3aK0+laUxNgBNBgEL0jW/831I+oapd6A==", + "type": "package", + "path": "blazored.typeahead/4.7.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "blazored.typeahead.4.7.0.nupkg.sha512", + "blazored.typeahead.nuspec", + "build/Blazored.Typeahead.props", + "build/Microsoft.AspNetCore.StaticWebAssets.props", + "buildMultiTargeting/Blazored.Typeahead.props", + "buildTransitive/Blazored.Typeahead.props", + "icon.png", + "lib/net6.0/Blazored.Typeahead.dll", + "staticwebassets/blazored-typeahead.css", + "staticwebassets/blazored-typeahead.js" + ] + }, "EPPlus/7.5.2": { "sha512": "qHJurPvgWoheHyyam53NV8d2CiOO2q88Rg/Lk0wSYwi/aoGDtzYihTMCHeTwGM9zHZnnI3aVLu482SODN+HB4g==", "type": "package", @@ -2691,6 +2748,7 @@ "net8.0": [ "Blazored.Modal >= 7.3.1", "Blazored.Toast >= 4.2.1", + "Blazored.Typeahead >= 4.7.0", "Domain >= 1.0.0", "Microsoft.AspNetCore.Components.Authorization >= 8.0.6", "Microsoft.AspNetCore.Components.WebAssembly >= 8.0.6", @@ -2765,6 +2823,10 @@ "target": "Package", "version": "[4.2.1, )" }, + "Blazored.Typeahead": { + "target": "Package", + "version": "[4.7.0, )" + }, "Microsoft.AspNetCore.Components.Authorization": { "target": "Package", "version": "[8.0.6, )" diff --git a/phronCare.UIBlazor/phronCare.UIBlazor.csproj b/phronCare.UIBlazor/phronCare.UIBlazor.csproj index 9d23a0c..97f4b45 100644 --- a/phronCare.UIBlazor/phronCare.UIBlazor.csproj +++ b/phronCare.UIBlazor/phronCare.UIBlazor.csproj @@ -16,6 +16,7 @@ + diff --git a/phronCare.UIBlazor/wwwroot/index.html b/phronCare.UIBlazor/wwwroot/index.html index 8854890..f952f63 100644 --- a/phronCare.UIBlazor/wwwroot/index.html +++ b/phronCare.UIBlazor/wwwroot/index.html @@ -17,12 +17,12 @@ - + - +