Add Adjustment, Products & Tags.
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 6m18s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 6m18s
Quote Demo v1
This commit is contained in:
parent
6476cfd701
commit
f1bc764bbf
14
Core/Interfaces/IAdjustmentReasonDom.cs.cs
Normal file
14
Core/Interfaces/IAdjustmentReasonDom.cs.cs
Normal file
@ -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<IEnumerable<EAdjustmentReason>> GetAllActiveAsync();
|
||||
}
|
||||
}
|
||||
21
Core/Interfaces/ILookUpDom.cs
Normal file
21
Core/Interfaces/ILookUpDom.cs
Normal file
@ -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<IEnumerable<ELookUpItem>> CustomersListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> InstitutionsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> PatientsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> ProfessionalsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> PeopleListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> BussinessUnitsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<EProductLookupItem>> ProductsListAsync(string filter, int limit = 10);
|
||||
|
||||
}
|
||||
}
|
||||
@ -5,17 +5,43 @@ namespace Models.Interfaces
|
||||
{
|
||||
public interface IQuoteDom
|
||||
{
|
||||
#region Presupuestos
|
||||
Task<PagedResult<EQuoteHeader>> GetAllQuotesAsync(int page = 1, int pageSize = 50);
|
||||
Task<EQuoteHeader?> GetQuoteByIdAsync(int id);
|
||||
Task<IEnumerable<EQuoteHeader>> GetQuotesByCustomerAsync(int customerId);
|
||||
Task<PagedResult<EQuoteHeader>> SearchQuotesAsync(int? customerId,
|
||||
string? quoteNumber,int? professionalId, int? institutionId,
|
||||
int? patientId, DateTime? issueDateFrom,DateTime? issueDateTo,
|
||||
string? status, int page = 1, int pageSize = 50);
|
||||
|
||||
Task<PagedResult<EQuoteHeader>> SearchQuotesAsync(
|
||||
int? customerId,
|
||||
string? quoteNumber,
|
||||
int? professionalId,
|
||||
int? institutionId,
|
||||
int? patientId,
|
||||
DateTime? issueDateFrom,
|
||||
DateTime? issueDateTo,
|
||||
string? status,
|
||||
int page = 1,
|
||||
int pageSize = 50);
|
||||
Task<EQuoteHeader> CreateQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||
Task UpdateQuoteAsync(EQuoteHeader quote);
|
||||
Task DeleteQuoteAsync(int id);
|
||||
#endregion
|
||||
|
||||
#region Ajustes
|
||||
Task<IEnumerable<EQuoteAdjustment>> GetAdjustmentsByQuoteIdAsync(int quoteId);
|
||||
Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment);
|
||||
Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment);
|
||||
Task DeleteAdjustmentAsync(int adjustmentId);
|
||||
#endregion
|
||||
|
||||
#region Impuestos
|
||||
Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId);
|
||||
Task<EQuoteTax> AddTaxAsync(EQuoteTax tax);
|
||||
Task UpdateTaxAsync(EQuoteTax tax);
|
||||
Task DeleteTaxAsync(int taxId);
|
||||
#endregion
|
||||
|
||||
#region Exportación
|
||||
Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams);
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Core/Services/AdjustmentReasonService.cs
Normal file
32
Core/Services/AdjustmentReasonService.cs
Normal file
@ -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<IEnumerable<EAdjustmentReason>> GetAllActiveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetAllActiveAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{methodName} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
27
Core/Services/LookupService .cs
Normal file
27
Core/Services/LookupService .cs
Normal file
@ -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<IEnumerable<ELookUpItem>> CustomersListAsync(string filter, int limit = 10) => _repository.CustomersListAsync(filter);
|
||||
public Task<IEnumerable<ELookUpItem>> InstitutionsListAsync(string filter, int limit = 10) => _repository.InstitutionsListAsync(filter);
|
||||
public Task<IEnumerable<ELookUpItem>> PatientsListAsync(string filter, int limit = 10) => _repository.PatientsListAsync(filter);
|
||||
public Task<IEnumerable<ELookUpItem>> ProfessionalsListAsync(string filter, int limit = 10) => _repository.ProfessionalsListAsync(filter);
|
||||
public Task<IEnumerable<ELookUpItem>> PeopleListAsync(string filter, int limit = 10) => _repository.PeopleListAsync(filter);
|
||||
public Task<IEnumerable<ELookUpItem>> BussinessUnitsListAsync(string filter, int limit = 10) => _repository.BussinessUnitsListAsync(filter);
|
||||
public Task<IEnumerable<EProductLookupItem>> ProductsListAsync(string filter, int limit = 10) =>
|
||||
_repository.ProductsListAsync(filter);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -126,6 +126,28 @@ namespace PhronCare.Core.Services.Sales
|
||||
await _quoteHeaderRepository.DeleteAdjustmentAsync(adjustmentId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Impuestos
|
||||
public async Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId)
|
||||
{
|
||||
return await _quoteHeaderRepository.GetTaxesByQuoteIdAsync(quoteId);
|
||||
}
|
||||
|
||||
public async Task<EQuoteTax> 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<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams)
|
||||
@ -172,5 +194,6 @@ namespace PhronCare.Core.Services.Sales
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
15
Domain/Entities/EAdjustmentReason.cs
Normal file
15
Domain/Entities/EAdjustmentReason.cs
Normal file
@ -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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
8
Domain/Entities/ELookUpItem.cs
Normal file
8
Domain/Entities/ELookUpItem.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Domain.Entities
|
||||
{
|
||||
public class ELookUpItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Nombre { get; set; }
|
||||
}
|
||||
}
|
||||
13
Domain/Entities/EProductLookupItem.cs
Normal file
13
Domain/Entities/EProductLookupItem.cs
Normal file
@ -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}";
|
||||
}
|
||||
}
|
||||
@ -62,12 +62,17 @@
|
||||
public decimal? Exchangerate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total del presupuesto expresado en moneda extranjera
|
||||
/// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto
|
||||
/// </summary>
|
||||
public decimal? Netamount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales
|
||||
/// </summary>
|
||||
public decimal? TotalForeign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public decimal? Total { get; set; }
|
||||
|
||||
@ -116,93 +121,7 @@
|
||||
public virtual ICollection<EQuoteDetail> PhSQuoteDetails { get; set; } = new List<EQuoteDetail>();
|
||||
|
||||
public virtual ICollection<EQuoteRole> PhSQuoteRoles { get; set; } = new List<EQuoteRole>();
|
||||
|
||||
public virtual ICollection<EQuoteTax> PhSQuoteTaxes { get; set; } = new List<EQuoteTax>();
|
||||
}
|
||||
|
||||
//public class EQuoteHeader
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// ID interno
|
||||
// /// </summary>
|
||||
// public int Id { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Relación con Tickets
|
||||
// /// </summary>
|
||||
// public Guid TicketId { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Cliente asociado
|
||||
// /// </summary>
|
||||
// public int CustomerId { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Unidad de negocio
|
||||
// /// </summary>
|
||||
// public int BusinessunitId { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Identificador único del vendedor
|
||||
// /// </summary>
|
||||
// public int PeopleId { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc.
|
||||
// /// </summary>
|
||||
// public string Status { get; set; } = null!;
|
||||
|
||||
// /// <summary>
|
||||
// /// Fecha de emisión
|
||||
// /// </summary>
|
||||
// public DateTime Issuedate { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Fecha de aprobación
|
||||
// /// </summary>
|
||||
// public DateOnly? Approvaldate { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Fecha tentativa (de cirugía por ej.)
|
||||
// /// </summary>
|
||||
// public DateTime? Estimateddate { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Importe estimado total
|
||||
// /// </summary>
|
||||
// public decimal? Estimatedamount { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Importe aprobado
|
||||
// /// </summary>
|
||||
// public decimal? Approvedamount { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Número visible del presupuesto
|
||||
// /// </summary>
|
||||
// public string Quotenumber { get; set; } = null!;
|
||||
|
||||
// /// <summary>
|
||||
// /// Cantidad de impresiones
|
||||
// /// </summary>
|
||||
// public int Printcount { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Observaciones internas
|
||||
// /// </summary>
|
||||
// public string? Observations { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Fecha de creación
|
||||
// /// </summary>
|
||||
// public DateTime Createdat { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// Fecha de modificación
|
||||
// /// </summary>
|
||||
// public DateTime? Modifiedat { get; set; }
|
||||
|
||||
// public virtual ICollection<EQuoteDetail> PhSQuoteDetails { get; set; } = new List<EQuoteDetail>();
|
||||
|
||||
// public virtual ICollection<EQuoteRole> PhSQuoteRoles { get; set; } = new List<EQuoteRole>();
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
57
Domain/Entities/EQuoteTax.cs
Normal file
57
Domain/Entities/EQuoteTax.cs
Normal file
@ -0,0 +1,57 @@
|
||||
namespace Domain.Entities
|
||||
{
|
||||
public class EQuoteTax
|
||||
{
|
||||
/// <summary>
|
||||
/// Clave primaria autoincremental
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Referencia al presupuesto (PhS_QuoteHeaders)
|
||||
/// </summary>
|
||||
public int QuoteheaderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nombre descriptivo del impuesto
|
||||
/// </summary>
|
||||
public string Taxname { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Código o identificador oficial del impuesto (ej. AFIP)
|
||||
/// </summary>
|
||||
public string? Taxcode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Base imponible del impuesto (importe gravado)
|
||||
/// </summary>
|
||||
public decimal TaxableAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Alícuota aplicada (porcentaje)
|
||||
/// </summary>
|
||||
public decimal Taxrate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importe final del impuesto calculado
|
||||
/// </summary>
|
||||
public decimal Taxamount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si el impuesto está incluido en el precio final (1 = sí)
|
||||
/// </summary>
|
||||
public bool IsIncludedInPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de creación del registro
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de modificación del registro
|
||||
/// </summary>
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.1</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
|
||||
|
||||
9
Models/Interfaces/IPhSAdjustmentReasonRepository.cs
Normal file
9
Models/Interfaces/IPhSAdjustmentReasonRepository.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Domain.Entities;
|
||||
|
||||
namespace Models.Interfaces
|
||||
{
|
||||
public interface IPhSAdjustmentReasonRepository
|
||||
{
|
||||
Task<IEnumerable<EAdjustmentReason>> GetAllActiveAsync();
|
||||
}
|
||||
}
|
||||
15
Models/Interfaces/IPhSLookUpRepository.cs
Normal file
15
Models/Interfaces/IPhSLookUpRepository.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Domain.Entities;
|
||||
|
||||
namespace Models.Interfaces
|
||||
{
|
||||
public interface IPhSLookUpRepository
|
||||
{
|
||||
Task<IEnumerable<ELookUpItem>> CustomersListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> InstitutionsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> PatientsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> ProfessionalsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> PeopleListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<ELookUpItem>> BussinessUnitsListAsync(string filter, int limit = 10);
|
||||
Task<IEnumerable<EProductLookupItem>> ProductsListAsync(string filter, int limit = 10);
|
||||
}
|
||||
}
|
||||
@ -21,5 +21,24 @@ namespace Models.Interfaces
|
||||
Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment);
|
||||
Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment);
|
||||
Task DeleteAdjustmentAsync(int adjustmentId);
|
||||
/// <summary>
|
||||
/// Obtiene todos los impuestos asociados a un presupuesto dado por su ID.
|
||||
/// </summary>
|
||||
Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId);
|
||||
|
||||
/// <summary>
|
||||
/// Agrega un nuevo impuesto al presupuesto correspondiente.
|
||||
/// </summary>
|
||||
Task<EQuoteTax> AddTaxAsync(EQuoteTax tax);
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza los datos de un impuesto existente en un presupuesto.
|
||||
/// </summary>
|
||||
Task UpdateTaxAsync(EQuoteTax tax);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina un impuesto asociado a un presupuesto a partir de su ID.
|
||||
/// </summary>
|
||||
Task DeleteTaxAsync(int taxId);
|
||||
}
|
||||
}
|
||||
|
||||
40
Models/Models/PhOhArcataxType.cs
Normal file
40
Models/Models/PhOhArcataxType.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Tabla de referencia de tipos de impuestos según AFIP para operaciones de venta.
|
||||
/// </summary>
|
||||
public partial class PhOhArcataxType
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único del impuesto.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código oficial del impuesto según AFIP.
|
||||
/// </summary>
|
||||
public int TaxCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Descripción del impuesto.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Indica si el impuesto está activo (1) o inactivo (0).
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de creación del registro.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de última modificación del registro.
|
||||
/// </summary>
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
}
|
||||
@ -64,12 +64,17 @@ public partial class PhSQuoteHeader
|
||||
public decimal? Exchangerate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total del presupuesto expresado en moneda extranjera
|
||||
/// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto
|
||||
/// </summary>
|
||||
public decimal? Netamount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales
|
||||
/// </summary>
|
||||
public decimal? TotalForeign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public decimal? Total { get; set; }
|
||||
|
||||
@ -118,4 +123,6 @@ public partial class PhSQuoteHeader
|
||||
public virtual ICollection<PhSQuoteDetail> PhSQuoteDetails { get; set; } = new List<PhSQuoteDetail>();
|
||||
|
||||
public virtual ICollection<PhSQuoteRole> PhSQuoteRoles { get; set; } = new List<PhSQuoteRole>();
|
||||
|
||||
public virtual ICollection<PhSQuoteTaxis> PhSQuoteTaxes { get; set; } = new List<PhSQuoteTaxis>();
|
||||
}
|
||||
|
||||
59
Models/Models/PhSQuoteTaxis.cs
Normal file
59
Models/Models/PhSQuoteTaxis.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhSQuoteTaxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Clave primaria autoincremental
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Referencia al presupuesto (PhS_QuoteHeaders)
|
||||
/// </summary>
|
||||
public int QuoteheaderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nombre descriptivo del impuesto
|
||||
/// </summary>
|
||||
public string Taxname { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Código o identificador oficial del impuesto (ej. AFIP)
|
||||
/// </summary>
|
||||
public string? Taxcode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Base imponible del impuesto (importe gravado)
|
||||
/// </summary>
|
||||
public decimal TaxableAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Alícuota aplicada (porcentaje)
|
||||
/// </summary>
|
||||
public decimal Taxrate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importe final del impuesto calculado
|
||||
/// </summary>
|
||||
public decimal Taxamount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si el impuesto está incluido en el precio final (1 = sí)
|
||||
/// </summary>
|
||||
public bool IsIncludedInPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de creación del registro
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de modificación del registro
|
||||
/// </summary>
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
|
||||
public virtual PhSQuoteHeader Quoteheader { get; set; } = null!;
|
||||
}
|
||||
@ -17,6 +17,8 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
|
||||
public virtual DbSet<PhOhArcadocumentType> PhOhArcadocumentTypes { get; set; }
|
||||
|
||||
public virtual DbSet<PhOhArcataxType> PhOhArcataxTypes { get; set; }
|
||||
|
||||
public virtual DbSet<PhOhTaxCondition> PhOhTaxConditions { get; set; }
|
||||
|
||||
public virtual DbSet<PhOhTicket> PhOhTickets { get; set; }
|
||||
@ -65,6 +67,8 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
|
||||
public virtual DbSet<PhSQuoteRole> PhSQuoteRoles { get; set; }
|
||||
|
||||
public virtual DbSet<PhSQuoteTaxis> 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<PhOhArcataxType>(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<PhOhTaxCondition>(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<PhSQuoteTaxis>(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);
|
||||
}
|
||||
|
||||
|
||||
25
Models/Repositories/PhSAdjustmentReasonRepository.cs
Normal file
25
Models/Repositories/PhSAdjustmentReasonRepository.cs
Normal file
@ -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<IEnumerable<EAdjustmentReason>> GetAllActiveAsync()
|
||||
{
|
||||
var adjustement= await _context.PhSAdjustmentReasons
|
||||
.Where(x => x.Active)
|
||||
.OrderBy(x => x.Code)
|
||||
.ToListAsync();
|
||||
return adjustement.Select(EntityMapper.MapEntity<PhSAdjustmentReason, EAdjustmentReason>);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
83
Models/Repositories/PhSLookUpRepository.cs
Normal file
83
Models/Repositories/PhSLookUpRepository.cs
Normal file
@ -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<IEnumerable<ELookUpItem>> 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<IEnumerable<ELookUpItem>> 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<IEnumerable<ELookUpItem>> 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<IEnumerable<ELookUpItem>> 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<IEnumerable<ELookUpItem>> 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<IEnumerable<ELookUpItem>> 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<IEnumerable<EProductLookupItem>> 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();
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<PhSQuoteHeader, EQuoteHeader>(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<PhSQuoteHeader, EQuoteHeader>);
|
||||
@ -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
|
||||
// ----------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene todos los impuestos asociados a un presupuesto dado por su ID.
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId)
|
||||
{
|
||||
var taxes = await _context.PhSQuoteTaxes
|
||||
.Where(t => t.QuoteheaderId == quoteId)
|
||||
.ToListAsync();
|
||||
|
||||
return taxes.Select(EntityMapper.MapEntity<PhSQuoteTaxis, EQuoteTax>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Agrega un nuevo impuesto al presupuesto correspondiente.
|
||||
/// </summary>
|
||||
public async Task<EQuoteTax> AddTaxAsync(EQuoteTax tax)
|
||||
{
|
||||
var dbEntity = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
||||
_context.PhSQuoteTaxes.Add(dbEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
return EntityMapper.MapEntity<PhSQuoteTaxis, EQuoteTax>(dbEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza los datos de un impuesto existente en un presupuesto.
|
||||
/// </summary>
|
||||
public async Task UpdateTaxAsync(EQuoteTax tax)
|
||||
{
|
||||
var dbEntity = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
||||
_context.PhSQuoteTaxes.Update(dbEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elimina un impuesto asociado a un presupuesto a partir de su ID.
|
||||
/// </summary>
|
||||
public async Task DeleteTaxAsync(int taxId)
|
||||
{
|
||||
var entity = await _context.PhSQuoteTaxes.FindAsync(taxId);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.PhSQuoteTaxes.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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.1</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\maski\.nuget\packages\" />
|
||||
|
||||
@ -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<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _adjustmentReasonDom.GetAllActiveAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
phronCare.API/Controllers/Sales/LookUpController.cs
Normal file
38
phronCare.API/Controllers/Sales/LookUpController.cs
Normal file
@ -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<IEnumerable<ELookUpItem>> Customers([FromQuery] string q)
|
||||
=> _lookup.CustomersListAsync(q);
|
||||
[HttpGet("institutions")]
|
||||
public Task<IEnumerable<ELookUpItem>> Institutions([FromQuery] string q)
|
||||
=> _lookup.InstitutionsListAsync(q);
|
||||
[HttpGet("patients")]
|
||||
public Task<IEnumerable<ELookUpItem>> Patients([FromQuery] string q)
|
||||
=> _lookup.PatientsListAsync(q);
|
||||
[HttpGet("people")]
|
||||
public Task<IEnumerable<ELookUpItem>> People([FromQuery] string q)
|
||||
=> _lookup.PeopleListAsync(q);
|
||||
[HttpGet("professionals")]
|
||||
public Task<IEnumerable<ELookUpItem>> Professionals([FromQuery] string q)
|
||||
=> _lookup.ProfessionalsListAsync(q);
|
||||
[HttpGet("bussinessunits")]
|
||||
public Task<IEnumerable<ELookUpItem>> BussinessUnits([FromQuery] string q)
|
||||
=> _lookup.BussinessUnitsListAsync(q);
|
||||
|
||||
[HttpGet("products")]
|
||||
public Task<IEnumerable<EProductLookupItem>> Products([FromQuery] string q)
|
||||
=> _lookup.ProductsListAsync(q);
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<IActionResult> 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<IActionResult> 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<IActionResult> ExportFiltered([FromBody] QuoteSearchParams searchParams)
|
||||
{
|
||||
@ -161,5 +170,76 @@ namespace phronCare.API.Controllers.Sales
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Impuestos (QuoteTaxes)
|
||||
|
||||
[HttpGet("{quoteId:int}/taxes")]
|
||||
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,10 +237,18 @@ static void RepositorysAndServices(WebApplicationBuilder builder)
|
||||
builder.Services.AddScoped<IPeopleDom, PeopleService>();
|
||||
builder.Services.AddScoped<IPhSPeopleRepository, PhSPeopleRepository>();
|
||||
|
||||
builder.Services.AddScoped<IAdjustmentReasonDom, AdjustmentReasonService>();
|
||||
builder.Services.AddScoped<IPhSAdjustmentReasonRepository, PhSAdjustmentReasonRepository>();
|
||||
|
||||
builder.Services.AddScoped<IQuoteDom, QuoteService>();
|
||||
|
||||
builder.Services.AddScoped<IPhSQuoteHeaderRepository, PhSQuoteHeaderRepository>();
|
||||
builder.Services.AddScoped<IPhSQuoteDetailRepository, PhSQuoteDetailRepository>();
|
||||
builder.Services.AddScoped<IPhSQuoteRoleRepository, PhSQuoteRoleRepository>();
|
||||
builder.Services.AddScoped<IPhSFormSeriesRepository, PhSFormSeriesRepository>();
|
||||
|
||||
// Registrar el service de lookup
|
||||
builder.Services.AddScoped<ILookUpDom, LookupService>();
|
||||
builder.Services.AddScoped<IPhSLookUpRepository, PhSLookUpRepository>();
|
||||
|
||||
}
|
||||
@ -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",
|
||||
|
||||
108
phronCare.UIBlazor/Pages/Sales/Modals/ProductSelectorModal.razor
Normal file
108
phronCare.UIBlazor/Pages/Sales/Modals/ProductSelectorModal.razor
Normal file
@ -0,0 +1,108 @@
|
||||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
||||
@using System.Globalization
|
||||
|
||||
@inject Services.Lookups.ISalesLookupService SalesLookupService
|
||||
|
||||
<div style="zoom: 80%;">
|
||||
<div class="mb-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-search me-2"></i>Buscar producto
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<input type="text"
|
||||
class="form-control mb-3"
|
||||
placeholder="Buscar productos..."
|
||||
value="@_searchTerm"
|
||||
@oninput="OnInputChanged" />
|
||||
|
||||
@if (_products is null)
|
||||
{
|
||||
<p class="text-muted"><em>Escriba al menos 3 caracteres para buscar.</em></p>
|
||||
}
|
||||
else if (!_products.Any())
|
||||
{
|
||||
<p class="text-danger"><em>No se encontraron productos.</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<table class="table table-bordered table-hover table-sm align-middle">
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th style="width: 10%">Código</th>
|
||||
<th>Descripción</th>
|
||||
<th class="text-center" style="width: 15%">Precio</th>
|
||||
<th style="width: 10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var product in _products)
|
||||
{
|
||||
<tr>
|
||||
<td><span class="badge bg-light text-dark">@product.Code</span></td>
|
||||
<td>@product.Description</td>
|
||||
<td class="text-center">$ @product.UnitPrice.ToString("N2")</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-success" @onclick="() => SelectProduct(product)">Seleccionar</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="text-end mt-3">
|
||||
<button class="btn btn-secondary" @onclick="Cancelar">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!;
|
||||
|
||||
private List<EProductLookupItem>? _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();
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
<EditForm Model="_model" OnValidSubmit="HandleValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="card" style="zoom: 80%;">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Nuevo Profesional</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label">Nombre Completo *</label>
|
||||
<InputText class="form-control" @bind-Value="_model.Fullname" />
|
||||
<ValidationMessage For="@(() => _model.Fullname)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
@* <label class="form-label">Tipo Doc *</label>
|
||||
<InputSelect class="form-select" @bind-Value="_model.DocumenttypeName">
|
||||
<option value="">Seleccione...</option>
|
||||
<option value="DNI">DNI</option>
|
||||
<option value="CUIL">CUIL</option>
|
||||
<option value="LE">LE</option>
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.DocumenttypeName)" /> *@
|
||||
<label for="DocumentType" class="form-label">Tipo Doc *</label>
|
||||
<InputSelect id="DocumentType" @bind-Value="_model.DocumenttypeName" class="form-control">
|
||||
<option value="">Seleccione...</option>
|
||||
@foreach (var type in documentTypes)
|
||||
{
|
||||
<option value="@type.Code">@type.Description</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.DocumenttypeName)" />
|
||||
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Número *</label>
|
||||
<InputText class="form-control" @bind-Value="_model.DocumentNumber" />
|
||||
<ValidationMessage For="@(() => _model.DocumentNumber)" />
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Matrícula</label>
|
||||
<InputText class="form-control" @bind-Value="_model.License" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="Type" class="form-label">Tipo de Profesional</label>
|
||||
<InputSelect id="Type" class="form-select" @bind-Value="_model.Type">
|
||||
@foreach (var option in professionalTypes)
|
||||
{
|
||||
<option value="@option.Value">@option.Text</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.Type)" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="Specialtyid" class="form-label">Especialidad</label>
|
||||
<InputSelect id="Specialtyid" class="form-select" @bind-Value="_model.SpecialtyId">
|
||||
<option value="">--- Seleccionar ---</option>
|
||||
@foreach (var s in specialties)
|
||||
{
|
||||
<option value="@s.Id">@s.Name</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.SpecialtyId)" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<button type="submit" class="btn btn-success">Guardar</button>
|
||||
<button type="button" class="btn btn-secondary ms-2" @onclick="Cancelar">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!;
|
||||
private List<EDocumentType> documentTypes = new();
|
||||
private List<EProfessionalSpecialty> 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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
||||
@using Domain.Entities
|
||||
@inject IToastService Toast
|
||||
@inject Services.Lookups.ISalesLookupService SalesLookupService
|
||||
|
||||
<EditForm Model="_model" OnValidSubmit="HandleValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="card" style="zoom: 90%;">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Agregar Ajuste</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Motivo *</label>
|
||||
<InputSelect class="form-select" @bind-Value="_model.ReasonCode">
|
||||
<option value="">Seleccione...</option>
|
||||
@foreach (var reason in _adjustmentReasons)
|
||||
{
|
||||
<option value="@reason.Code">@reason.Description</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => _model.ReasonCode)" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Monto *</label>
|
||||
<InputNumber class="form-control" @bind-Value="_model.Amount" />
|
||||
<ValidationMessage For="@(() => _model.Amount)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer text-end">
|
||||
<button type="submit" class="btn btn-success">Agregar</button>
|
||||
<button type="button" class="btn btn-secondary ms-2" @onclick="Cancelar">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!;
|
||||
|
||||
private QuoteAdjustmentDto _model = new();
|
||||
private List<EAdjustmentReason> _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; }
|
||||
}
|
||||
}
|
||||
373
phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor
Normal file
373
phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor
Normal file
@ -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
|
||||
|
||||
<EditForm Model="_quoteModel">
|
||||
<div class="container mt-4" style="zoom:0.8;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="mb-0">Emisión de Presupuesto</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- FILA 1: Cliente, Vendedor -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 add-wrapper">
|
||||
<label class="form-label">Cliente *</label>
|
||||
<BlazoredTypeahead TItem="ELookUpItem" TValue="ELookUpItem"
|
||||
SearchMethod="SalesLookupService.SearchCustomersAsync"
|
||||
Value="_selectedCustomer"
|
||||
ValueChanged="OnCustomerSelected"
|
||||
ValueExpression="@(() => _selectedCustomer)"
|
||||
MaximumSuggestions="5" Placeholder="Buscar cliente..."
|
||||
TextProperty="Nombre">
|
||||
<ResultTemplate Context="customer">@customer.Nombre</ResultTemplate>
|
||||
<SelectedTemplate Context="customer">@customer.Nombre</SelectedTemplate>
|
||||
</BlazoredTypeahead>
|
||||
<button type="button" class="add-btn" title="Agregar nuevo">+</button>
|
||||
</div>
|
||||
<div class="col-md-6 add-wrapper">
|
||||
<label class="form-label">Vendedor *</label>
|
||||
<BlazoredTypeahead TItem="ELookUpItem" TValue="ELookUpItem"
|
||||
SearchMethod="SalesLookupService.SearchPeopleAsync"
|
||||
Value="_selectedPerson"
|
||||
ValueChanged="OnPersonSelected"
|
||||
ValueExpression="@(() => _selectedPerson)"
|
||||
MaximumSuggestions="5" Placeholder="Buscar vendedor..."
|
||||
TextProperty="Nombre">
|
||||
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
|
||||
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
|
||||
</BlazoredTypeahead>
|
||||
<button type="button" class="add-btn" title="Agregar vendedor">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FILA 2: Profesional, Paciente -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 add-wrapper">
|
||||
<label class="form-label">Profesional *</label>
|
||||
<BlazoredTypeahead TItem="ELookUpItem" TValue="ELookUpItem"
|
||||
SearchMethod="SalesLookupService.SearchProfessionalsAsync"
|
||||
Value="_selectedProfessional"
|
||||
ValueChanged="OnProfessionalSelected"
|
||||
ValueExpression="@(() => _selectedProfessional)"
|
||||
MaximumSuggestions="5" Placeholder="Buscar profesional..."
|
||||
TextProperty="Nombre">
|
||||
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
|
||||
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
|
||||
</BlazoredTypeahead>
|
||||
<button type="button" class="add-btn" title="Agregar profesional">+</button>
|
||||
<button type="button" class="add-btn" title="Agregar profesional" @onclick="AddNewProfessional">+</button>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6 add-wrapper">
|
||||
<label class="form-label">Paciente *</label>
|
||||
<BlazoredTypeahead TItem="ELookUpItem" TValue="ELookUpItem"
|
||||
SearchMethod="SalesLookupService.SearchPatientsAsync"
|
||||
Value="_selectedPatient"
|
||||
ValueChanged="OnPatientSelected"
|
||||
ValueExpression="@(() => _selectedPatient)"
|
||||
MaximumSuggestions="5" Placeholder="Buscar paciente..."
|
||||
TextProperty="Nombre">
|
||||
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
|
||||
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
|
||||
</BlazoredTypeahead>
|
||||
<button type="button" class="add-btn" title="Agregar paciente">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FILA 3: Institución, Unidad de negocio -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 add-wrapper">
|
||||
<label class="form-label">Institución *</label>
|
||||
<BlazoredTypeahead TItem="ELookUpItem" TValue="ELookUpItem"
|
||||
SearchMethod="SalesLookupService.SearchInstitutionsAsync"
|
||||
Value="_selectedInstitution"
|
||||
ValueChanged="OnInstitutionSelected"
|
||||
ValueExpression="@(() => _selectedInstitution)"
|
||||
MaximumSuggestions="5" Placeholder="Buscar institución..."
|
||||
TextProperty="Nombre">
|
||||
<ResultTemplate Context="item">@item.Nombre</ResultTemplate>
|
||||
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
|
||||
</BlazoredTypeahead>
|
||||
<button type="button" class="add-btn" title="Agregar institución">+</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Unidad de negocio *</label>
|
||||
<InputSelect class="form-select" @bind-Value="_quoteModel.BusinessunitId">
|
||||
<option disabled selected value="">Seleccione...</option>
|
||||
@foreach (var unidad in _businessUnits)
|
||||
{
|
||||
<option value="@unidad.Id">@unidad.Nombre</option>
|
||||
}
|
||||
</InputSelect>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FILA 4: Moneda, Cambio, OutOfTown -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Moneda</label>
|
||||
<InputSelect class="form-select" @bind-Value="_quoteModel.Currency">
|
||||
<option value="ARS">ARS</option>
|
||||
<option value="USD">USD</option>
|
||||
</InputSelect>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Tipo de cambio</label>
|
||||
<InputNumber class="form-control" @bind-Value="_quoteModel.Exchangerate" />
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<div class="form-check form-switch">
|
||||
<InputCheckbox id="OutOfTown" class="form-check-input" @bind-Value="_quoteModel.OutOfTown" />
|
||||
<label class="form-check-label ms-2" for="OutOfTown">¿Fuera de localidad?</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FILA 5: Instrucciones y Observaciones -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Instrucciones de despacho</label>
|
||||
<InputText class="form-control" @bind-Value="_quoteModel.DispatchInstruction" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Observaciones</label>
|
||||
<InputText class="form-control" @bind-Value="_quoteModel.Observations" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Productos Cotizados -->
|
||||
<hr />
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h5 class="mb-3">Productos Cotizados</h5>
|
||||
|
||||
<button type="button" class="btn btn-outline-success mb-2" @onclick="AddNewProduct">
|
||||
<i class="fas fa-cart-plus me-1"></i> Agregar producto
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 15%;">Producto</th>
|
||||
<th>Descripción</th>
|
||||
<th style="width: 10%;">Cantidad</th>
|
||||
<th style="width: 15%;">Precio Unitario</th>
|
||||
<th style="width: 15%;">Total</th>
|
||||
<th style="width: 5%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (_quoteModel.PhSQuoteDetails.Any())
|
||||
{
|
||||
foreach (var item in _quoteModel.PhSQuoteDetails)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.ProductId</td>
|
||||
<td>
|
||||
<InputTextArea class="form-control"
|
||||
style="resize: vertical;"
|
||||
@bind-Value="item.ProductDescription"
|
||||
Rows="3" @oninput="RecalculateTotals" />
|
||||
</td>
|
||||
<td>
|
||||
<InputNumber class="form-control"
|
||||
Value="item.Quantity"
|
||||
ValueChanged="(int val) => OnValueChanged(item, (int)val, (i, v) => i.Quantity = v)"
|
||||
ValueExpression="@(() => item.Quantity)" />
|
||||
</td>
|
||||
<td>
|
||||
<InputNumber class="form-control"
|
||||
Value="item.Unitprice"
|
||||
ValueChanged="(decimal val) =>OnValueChanged(item, (decimal)val, (i, v) => i.Unitprice = v)"
|
||||
ValueExpression="@(() => item.Unitprice)" />
|
||||
</td>
|
||||
<td>$ @($"{item.Quantity * item.Unitprice:0.00}")</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-danger" @onclick="() => RemoveDetail(item)">🗑</button>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<tr>
|
||||
<td colspan="5"><em>No hay productos agregados.</em></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Totales + Ajustes -->
|
||||
<div class="row justify-content-end mt-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label d-block">Ajustes comerciales</label>
|
||||
|
||||
@if (_quoteModel.PhSQuoteAdjustments.Any())
|
||||
{
|
||||
<div class="mb-2">
|
||||
@foreach (var adj in _quoteModel.PhSQuoteAdjustments)
|
||||
{
|
||||
<span class="adjustment-tag">
|
||||
@adj.ReasonCode
|
||||
<button type="button" class="remove-btn" title="Eliminar" @onclick="() => RemoveAdjustment(adj)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-muted">Sin ajustes.</p>
|
||||
}
|
||||
|
||||
<button class="btn btn-sm btn-outline-success mt-2" @onclick="OpenAdjustmentModal">
|
||||
<i class="fas fa-plus me-1"></i> Agregar ajuste
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
<span>Subtotal</span>
|
||||
<strong>$ @_netAmount.ToString("N2")</strong>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
<span>Taxes</span>
|
||||
<strong>$ @_taxAmount.ToString("N2")</strong>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
<span>Total</span>
|
||||
<strong>$ @_grandTotal.ToString("N2")</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
<button type="button" class="btn btn-secondary ms-2">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
private EQuoteHeader _quoteModel = new();
|
||||
private ELookUpItem? _selectedCustomer;
|
||||
private ELookUpItem? _selectedProfessional;
|
||||
private ELookUpItem? _selectedInstitution;
|
||||
private ELookUpItem? _selectedPatient;
|
||||
private ELookUpItem? _selectedPerson;
|
||||
private List<ELookUpItem> _businessUnits = new();
|
||||
private decimal _netAmount = 0;
|
||||
private decimal _taxAmount = 0;
|
||||
private decimal _grandTotal = 0;
|
||||
|
||||
private Task OnValueChanged<T>(EQuoteDetail item, T value, Action<EQuoteDetail, T> 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<ProductSelectorModal>("", 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<ProfessionalQuickAddModal>(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<ELookUpItem?> setSelected, Action<int>? 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<QuoteAdjustmentQuickAddModal>("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);
|
||||
}
|
||||
|
||||
}
|
||||
56
phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor.css
Normal file
56
phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor.css
Normal file
@ -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 */
|
||||
}
|
||||
|
||||
@ -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>("#app");
|
||||
@ -37,19 +39,7 @@ if (config != null)
|
||||
}
|
||||
#endregion
|
||||
#region Injection Dependencis
|
||||
builder.Services.AddScoped<TicketsService>();
|
||||
builder.Services.AddScoped<CustomerService>();
|
||||
builder.Services.AddScoped<TaxConditionService>();
|
||||
builder.Services.AddScoped<AccountTypeService>();
|
||||
builder.Services.AddScoped<PatientService>();
|
||||
builder.Services.AddScoped<DocumentTypeService>();
|
||||
builder.Services.AddScoped<InstitutionService>();
|
||||
builder.Services.AddScoped<ProductService>();
|
||||
builder.Services.AddScoped<BusinessUnitService>();
|
||||
builder.Services.AddScoped<ProfessionalService>();
|
||||
builder.Services.AddScoped<PeopleService>();
|
||||
builder.Services.AddScoped<ProfessionalSpecialtyService>();
|
||||
builder.Services.AddScoped<ProductCategoryService>();
|
||||
InjectDependencies(builder);
|
||||
#endregion
|
||||
#region UI
|
||||
builder.Services.AddBlazoredModal();
|
||||
@ -58,3 +48,22 @@ builder.Services.AddSingleton<NavMenuService>();
|
||||
#endregion
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
|
||||
static void InjectDependencies(WebAssemblyHostBuilder builder)
|
||||
{
|
||||
builder.Services.AddScoped<ISalesLookupService, SalesLookupService>();
|
||||
|
||||
builder.Services.AddScoped<TicketsService>();
|
||||
builder.Services.AddScoped<CustomerService>();
|
||||
builder.Services.AddScoped<TaxConditionService>();
|
||||
builder.Services.AddScoped<AccountTypeService>();
|
||||
builder.Services.AddScoped<PatientService>();
|
||||
builder.Services.AddScoped<DocumentTypeService>();
|
||||
builder.Services.AddScoped<InstitutionService>();
|
||||
builder.Services.AddScoped<ProductService>();
|
||||
builder.Services.AddScoped<BusinessUnitService>();
|
||||
builder.Services.AddScoped<ProfessionalService>();
|
||||
builder.Services.AddScoped<PeopleService>();
|
||||
builder.Services.AddScoped<ProfessionalSpecialtyService>();
|
||||
builder.Services.AddScoped<ProductCategoryService>();
|
||||
}
|
||||
16
phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs
Normal file
16
phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Domain.Entities;
|
||||
|
||||
namespace phronCare.UIBlazor.Services.Lookups
|
||||
{
|
||||
public interface ISalesLookupService
|
||||
{
|
||||
Task<IEnumerable<ELookUpItem>> SearchCustomersAsync(string filtro);
|
||||
Task<IEnumerable<ELookUpItem>> SearchInstitutionsAsync(string filtro);
|
||||
Task<IEnumerable<ELookUpItem>> SearchPatientsAsync(string filtro);
|
||||
Task<IEnumerable<ELookUpItem>> SearchPeopleAsync(string filtro);
|
||||
Task<IEnumerable<ELookUpItem>> SearchProfessionalsAsync(string filtro);
|
||||
Task<IEnumerable<ELookUpItem>> SearchBussinessUnitsAsync(string filtro);
|
||||
Task<IEnumerable<EProductLookupItem>> SearchProductsAsync(string filtro);
|
||||
Task<IEnumerable<EAdjustmentReason>> GetAdjustmentReasonsAsync();
|
||||
}
|
||||
}
|
||||
53
phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs
Normal file
53
phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs
Normal file
@ -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<IEnumerable<ELookUpItem>> SearchCustomersAsync(string filtro)
|
||||
=> FetchAsync($"api/lookup/customers?q={Uri.EscapeDataString(filtro)}", filtro);
|
||||
public Task<IEnumerable<ELookUpItem>> SearchProfessionalsAsync(string filtro)
|
||||
=> FetchAsync($"api/lookup/professionals?q={Uri.EscapeDataString(filtro)}", filtro);
|
||||
public Task<IEnumerable<ELookUpItem>> SearchInstitutionsAsync(string filtro)
|
||||
=> FetchAsync($"api/lookup/institutions?q={Uri.EscapeDataString(filtro)}", filtro);
|
||||
public Task<IEnumerable<ELookUpItem>> SearchPatientsAsync(string filtro)
|
||||
=> FetchAsync($"api/lookup/patients?q={Uri.EscapeDataString(filtro)}", filtro);
|
||||
public Task<IEnumerable<ELookUpItem>> SearchPeopleAsync(string filtro)
|
||||
=> FetchAsync($"api/lookup/people?q={Uri.EscapeDataString(filtro)}", filtro);
|
||||
public async Task<IEnumerable<ELookUpItem>> SearchBussinessUnitsAsync(string filtro)
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<List<EBusinessUnit>>("api/businessunit/all");
|
||||
|
||||
return result?.Select(b => new ELookUpItem
|
||||
{
|
||||
Id = b.Id,
|
||||
Nombre = $"{b.Code} | {b.Description}"
|
||||
}) ?? Enumerable.Empty<ELookUpItem>();
|
||||
}
|
||||
public async Task<IEnumerable<ELookUpItem>> FetchAsync(string url,string filtro)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url) || filtro.Length < 3)
|
||||
return Array.Empty<ELookUpItem>();
|
||||
|
||||
var items = await _http.GetFromJsonAsync<ELookUpItem[]>(url);
|
||||
return items ?? Array.Empty<ELookUpItem>();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<EProductLookupItem>> SearchProductsAsync(string filtro)
|
||||
=> FetchProductsAsync($"api/lookup/products?q={Uri.EscapeDataString(filtro)}");
|
||||
public async Task<IEnumerable<EAdjustmentReason>> GetAdjustmentReasonsAsync()
|
||||
{
|
||||
var items = await _http.GetFromJsonAsync<EAdjustmentReason[]>("api/adjustmentreason/getall");
|
||||
return items ?? Array.Empty<EAdjustmentReason>();
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<EProductLookupItem>> FetchProductsAsync(string url)
|
||||
{
|
||||
var items = await _http.GetFromJsonAsync<EProductLookupItem[]>(url);
|
||||
return items ?? Array.Empty<EProductLookupItem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
<AuthorizeView>
|
||||
<Authorized Context="authContext">
|
||||
<div class="d-flex justify-content-end align-items-center gap-3 px-3 py-2">
|
||||
<span class="fw-bold text-dark">
|
||||
<span class="text-dark">
|
||||
Bienvenido, @GetUserDisplayName(authContext.User)
|
||||
</span>
|
||||
<a href="Logout" class="btn btn-sm btn-danger rounded-3 d-flex align-items-center gap-1 text-decoration-none">
|
||||
|
||||
@ -96,6 +96,12 @@
|
||||
<li aria-hidden="true"></li> Instituciones
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-1">
|
||||
<NavLink class="nav-link" href="quote/create">
|
||||
<li aria-hidden="true"></li> Presupuesto
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -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, )"
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.net.sdk.webassembly.pack\9.0.3\build\Microsoft.NET.Sdk.WebAssembly.Pack.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.sdk.webassembly.pack\9.0.3\build\Microsoft.NET.Sdk.WebAssembly.Pack.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.net.illink.tasks\8.0.14\build\Microsoft.NET.ILLink.Tasks.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.illink.tasks\8.0.14\build\Microsoft.NET.ILLink.Tasks.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.aspnetcore.components.webassembly\8.0.6\build\net8.0\Microsoft.AspNetCore.Components.WebAssembly.props" Condition="Exists('$(NuGetPackageRoot)microsoft.aspnetcore.components.webassembly\8.0.6\build\net8.0\Microsoft.AspNetCore.Components.WebAssembly.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazored.typeahead\4.7.0\buildTransitive\Blazored.Typeahead.props" Condition="Exists('$(NuGetPackageRoot)blazored.typeahead\4.7.0\buildTransitive\Blazored.Typeahead.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazored.toast\4.2.1\buildTransitive\Blazored.Toast.props" Condition="Exists('$(NuGetPackageRoot)blazored.toast\4.2.1\buildTransitive\Blazored.Toast.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)blazored.modal\7.3.1\buildTransitive\Blazored.Modal.props" Condition="Exists('$(NuGetPackageRoot)blazored.modal\7.3.1\buildTransitive\Blazored.Modal.props')" />
|
||||
</ImportGroup>
|
||||
|
||||
@ -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, )"
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.Modal" Version="7.3.1" />
|
||||
<PackageReference Include="Blazored.Toast" Version="4.2.1" />
|
||||
<PackageReference Include="Blazored.Typeahead" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
|
||||
|
||||
@ -17,12 +17,12 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
|
||||
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
|
||||
crossorigin="" />
|
||||
|
||||
<link href="_content/Blazored.Typeahead/blazored-typeahead.css" rel="stylesheet" />
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
|
||||
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
|
||||
crossorigin=""></script>
|
||||
|
||||
<script src="_content/Blazored.Typeahead/blazored-typeahead.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user