Add Save Cascade Quote Insert
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m39s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 5m39s
with Validations
This commit is contained in:
parent
09090bef21
commit
3d6af27e34
@ -20,29 +20,14 @@ namespace Models.Interfaces
|
|||||||
string? status,
|
string? status,
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 50);
|
int pageSize = 50);
|
||||||
Task<EQuoteHeader> CreateQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
|
||||||
Task UpdateQuoteAsync(EQuoteHeader quote);
|
Task UpdateQuoteAsync(EQuoteHeader quote);
|
||||||
Task DeleteQuoteAsync(int id);
|
Task DeleteQuoteAsync(int id);
|
||||||
#endregion
|
#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
|
#region Exportación
|
||||||
Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams);
|
Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams);
|
||||||
|
#endregion
|
||||||
|
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
||||||
Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9,16 +9,12 @@ namespace PhronCare.Core.Services.Sales
|
|||||||
{
|
{
|
||||||
public class QuoteService(
|
public class QuoteService(
|
||||||
IPhSQuoteHeaderRepository quoteHeaderRepository,
|
IPhSQuoteHeaderRepository quoteHeaderRepository,
|
||||||
IPhSQuoteDetailRepository quoteDetailRepository,
|
IPhSQuoteRepository quoteRepository
|
||||||
IPhSQuoteRoleRepository quoteRoleRepository,
|
|
||||||
IPhSFormSeriesRepository formSeriesRepository
|
|
||||||
) : IQuoteDom
|
) : IQuoteDom
|
||||||
{
|
{
|
||||||
#region Declaraciones
|
#region Declaraciones
|
||||||
private readonly IPhSQuoteHeaderRepository _quoteHeaderRepository = quoteHeaderRepository;
|
private readonly IPhSQuoteHeaderRepository _quoteHeaderRepository = quoteHeaderRepository;
|
||||||
private readonly IPhSQuoteDetailRepository _quoteDetailRepository = quoteDetailRepository;
|
private readonly IPhSQuoteRepository _quoteRepository = quoteRepository;
|
||||||
private readonly IPhSQuoteRoleRepository _quoteRoleRepository = quoteRoleRepository;
|
|
||||||
private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Presupuestos
|
#region Presupuestos
|
||||||
@ -26,17 +22,14 @@ namespace PhronCare.Core.Services.Sales
|
|||||||
{
|
{
|
||||||
return await _quoteHeaderRepository.GetAllAsync(page, pageSize);
|
return await _quoteHeaderRepository.GetAllAsync(page, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EQuoteHeader?> GetQuoteByIdAsync(int id)
|
public async Task<EQuoteHeader?> GetQuoteByIdAsync(int id)
|
||||||
{
|
{
|
||||||
return await _quoteHeaderRepository.GetByIdAsync(id);
|
return await _quoteHeaderRepository.GetByIdAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<EQuoteHeader>> GetQuotesByCustomerAsync(int customerId)
|
public async Task<IEnumerable<EQuoteHeader>> GetQuotesByCustomerAsync(int customerId)
|
||||||
{
|
{
|
||||||
return await _quoteHeaderRepository.GetByCustomerIdAsync(customerId);
|
return await _quoteHeaderRepository.GetByCustomerIdAsync(customerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResult<EQuoteHeader>> SearchQuotesAsync(
|
public async Task<PagedResult<EQuoteHeader>> SearchQuotesAsync(
|
||||||
int? customerId,
|
int? customerId,
|
||||||
string? quoteNumber,
|
string? quoteNumber,
|
||||||
@ -61,94 +54,16 @@ namespace PhronCare.Core.Services.Sales
|
|||||||
page,
|
page,
|
||||||
pageSize);
|
pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EQuoteHeader> CreateQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
|
||||||
{
|
|
||||||
// Obtener el próximo número de documento
|
|
||||||
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
|
|
||||||
quote.Quotenumber = nextNumber.ToString();
|
|
||||||
|
|
||||||
// Crear encabezado
|
|
||||||
var newQuote = await _quoteHeaderRepository.AddAsync(quote);
|
|
||||||
|
|
||||||
// Crear detalles asociados
|
|
||||||
if (quote.PhSQuoteDetails != null)
|
|
||||||
{
|
|
||||||
foreach (var detail in quote.PhSQuoteDetails)
|
|
||||||
{
|
|
||||||
detail.QuoteheaderId = newQuote.Id;
|
|
||||||
await _quoteDetailRepository.AddAsync(detail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear roles asociados
|
|
||||||
if (quote.PhSQuoteRoles != null)
|
|
||||||
{
|
|
||||||
foreach (var role in quote.PhSQuoteRoles)
|
|
||||||
{
|
|
||||||
role.QuoteheaderId = newQuote.Id;
|
|
||||||
await _quoteRoleRepository.AddAsync(role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newQuote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateQuoteAsync(EQuoteHeader quote)
|
public async Task UpdateQuoteAsync(EQuoteHeader quote)
|
||||||
{
|
{
|
||||||
await _quoteHeaderRepository.UpdateAsync(quote);
|
await _quoteHeaderRepository.UpdateAsync(quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteQuoteAsync(int id)
|
public async Task DeleteQuoteAsync(int id)
|
||||||
{
|
{
|
||||||
await _quoteHeaderRepository.DeleteAsync(id);
|
await _quoteHeaderRepository.DeleteAsync(id);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Ajustes
|
|
||||||
public async Task<IEnumerable<EQuoteAdjustment>> GetAdjustmentsByQuoteIdAsync(int quoteId)
|
|
||||||
{
|
|
||||||
return await _quoteHeaderRepository.GetAdjustmentsByQuoteIdAsync(quoteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment)
|
|
||||||
{
|
|
||||||
return await _quoteHeaderRepository.AddAdjustmentAsync(adjustment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment)
|
|
||||||
{
|
|
||||||
await _quoteHeaderRepository.UpdateAdjustmentAsync(adjustment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteAdjustmentAsync(int adjustmentId)
|
|
||||||
{
|
|
||||||
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
|
#region Exportación
|
||||||
public async Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams)
|
public async Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams)
|
||||||
{
|
{
|
||||||
@ -198,66 +113,65 @@ namespace PhronCare.Core.Services.Sales
|
|||||||
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
||||||
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
||||||
{
|
{
|
||||||
using var transaction = await _quoteHeaderRepository.BeginTransactionAsync();
|
// 1. Validaciones antes de iniciar transacción
|
||||||
|
ValidateQuote(quote);
|
||||||
|
return await _quoteRepository.CreateFullQuoteAsync(quote, formSeriesId);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
try
|
#region Validaciones QuoteCreate
|
||||||
|
private void ValidateQuote(EQuoteHeader quote)
|
||||||
{
|
{
|
||||||
// Obtener el próximo número de presupuesto desde la serie
|
if (quote == null)
|
||||||
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
|
throw new ArgumentNullException(nameof(quote), "El presupuesto no puede ser nulo.");
|
||||||
quote.Quotenumber = nextNumber.ToString();
|
|
||||||
|
|
||||||
// Crear encabezado principal
|
if (quote.CustomerId <= 0)
|
||||||
var headerEntity = await _quoteHeaderRepository.AddAsync(quote);
|
throw new ArgumentException("Debe seleccionar un cliente.");
|
||||||
|
|
||||||
|
if (quote.PeopleId <= 0)
|
||||||
|
throw new ArgumentException("Debe seleccionar un vendedor.");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(quote.Currency))
|
||||||
|
throw new ArgumentException("La moneda es obligatoria.");
|
||||||
|
|
||||||
|
if (quote.PhSQuoteDetails == null || !quote.PhSQuoteDetails.Any())
|
||||||
|
throw new InvalidOperationException("Debe incluir al menos un producto.");
|
||||||
|
|
||||||
// Crear detalles
|
|
||||||
if (quote.PhSQuoteDetails?.Any() == true)
|
|
||||||
{
|
|
||||||
foreach (var detail in quote.PhSQuoteDetails)
|
foreach (var detail in quote.PhSQuoteDetails)
|
||||||
{
|
{
|
||||||
detail.QuoteheaderId = headerEntity.Id;
|
if (detail.Quantity <= 0)
|
||||||
await _quoteDetailRepository.AddAsync(detail);
|
throw new ArgumentException($"La cantidad para el producto {detail.ProductId} debe ser mayor a cero.");
|
||||||
}
|
if (detail.Unitprice < 0)
|
||||||
|
throw new ArgumentException($"El precio unitario del producto {detail.ProductId} no puede ser negativo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear roles
|
if (quote.PhSQuoteRoles == null || !quote.PhSQuoteRoles.Any())
|
||||||
if (quote.PhSQuoteRoles?.Any() == true)
|
throw new InvalidOperationException("Debe asignar al menos un rol (profesional, paciente o institución).");
|
||||||
{
|
|
||||||
foreach (var role in quote.PhSQuoteRoles)
|
|
||||||
{
|
|
||||||
role.QuoteheaderId = headerEntity.Id;
|
|
||||||
await _quoteRoleRepository.AddAsync(role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear ajustes (rebajas)
|
var hasProfessional = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Professional);
|
||||||
if (quote.PhSQuoteAdjustments?.Any() == true)
|
var hasPatient = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Patient);
|
||||||
{
|
var hasInstitution = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Institution);
|
||||||
foreach (var adj in quote.PhSQuoteAdjustments)
|
|
||||||
{
|
|
||||||
adj.QuoteheaderId = headerEntity.Id;
|
|
||||||
await _quoteHeaderRepository.AddAdjustmentAsync(adj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear impuestos
|
if (!hasProfessional)
|
||||||
if (quote.PhSQuoteTaxes?.Any() == true)
|
throw new InvalidOperationException("Debe asignar un profesional.");
|
||||||
|
if (!hasInstitution)
|
||||||
|
throw new InvalidOperationException("Debe asignar un paciente.");
|
||||||
|
if (!hasPatient)
|
||||||
|
throw new InvalidOperationException("Debe asignar un paciente.");
|
||||||
|
|
||||||
|
if (quote.PhSQuoteTaxes != null)
|
||||||
{
|
{
|
||||||
foreach (var tax in quote.PhSQuoteTaxes)
|
foreach (var tax in quote.PhSQuoteTaxes)
|
||||||
{
|
{
|
||||||
tax.QuoteheaderId = headerEntity.Id;
|
if (tax.Taxrate < 0 || tax.Taxrate > 100)
|
||||||
await _quoteHeaderRepository.AddTaxAsync(tax);
|
throw new ArgumentException($"La alícuota del impuesto '{tax.Taxname}' no es válida.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await transaction.CommitAsync();
|
if (quote.Total < 0)
|
||||||
return headerEntity.Quotenumber;
|
throw new ArgumentException("El total del presupuesto no puede ser negativo.");
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await transaction.RollbackAsync();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Código del motivo de ajuste (FK a PhS_AdjustmentReasons)
|
/// Código del motivo de ajuste (FK a PhS_AdjustmentReasons)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ReasonCode { get; set; } = null!;
|
public string ReasonCode { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Importe del ajuste realizado (positivo o nulo)
|
/// Importe del ajuste realizado (positivo o nulo)
|
||||||
@ -28,7 +28,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Descripción adicional del ajuste
|
/// Descripción adicional del ajuste
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fecha de registro del ajuste
|
/// Fecha de registro del ajuste
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Descripción modificable del producto (puede diferir del original)
|
/// Descripción modificable del producto (puede diferir del original)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ProductDescription { get; set; }
|
public string? ProductDescription { get; set; }=String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cantidad
|
/// Cantidad
|
||||||
@ -52,8 +52,8 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? Modifiedat { get; set; }
|
public DateTime? Modifiedat { get; set; }
|
||||||
|
|
||||||
public virtual EProduct Product { get; set; } = null!;
|
//public virtual EProduct Product { get; set; } = null!;
|
||||||
|
|
||||||
public virtual EQuoteHeader PhSQuoteheader { get; set; } = null!;
|
//public virtual EQuoteHeader PhSQuoteheader { get; set; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
namespace Domain.Entities
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Domain.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tabla de cabeceras de presupuestos
|
/// Tabla de cabeceras de presupuestos
|
||||||
@ -19,7 +21,8 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Número visible del presupuesto
|
/// Número visible del presupuesto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Quotenumber { get; set; } = null!;
|
|
||||||
|
public string Quotenumber { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cliente asociado
|
/// Cliente asociado
|
||||||
@ -54,17 +57,17 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Código de moneda pactada (ISO 4217). Ej: ARS, USD
|
/// Código de moneda pactada (ISO 4217). Ej: ARS, USD
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Currency { get; set; } = null!;
|
public string Currency { get; set; }= String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tipo de cambio pactado para conversión a pesos argentinos
|
/// Tipo de cambio pactado para conversión a pesos argentinos
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal? Exchangerate { get; set; }
|
public decimal Exchangerate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto
|
/// Importe neto antes de aplicar impuestos, expresado en la moneda pactada del presupuesto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal? Netamount { get; set; }
|
public decimal Netamount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales
|
/// Importe total del presupuesto expresado en la moneda pactada (extranjera), incluyendo impuestos y ajustes comerciales
|
||||||
@ -84,7 +87,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc.
|
/// Estado: E (Emitido), A (Aprobado), AC (Aprobado para cirugia), etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Status { get; set; } = null!;
|
public string Status { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indica si la cirugía se realizará fuera de la ciudad/localidad habitual (“out of town”)
|
/// Indica si la cirugía se realizará fuera de la ciudad/localidad habitual (“out of town”)
|
||||||
@ -94,7 +97,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Instrucción dirigida al área de logística para detallar qué debe prepararse o despacharse (ej: “CMF 1.5 + INSTRUMENTAL”)
|
/// Instrucción dirigida al área de logística para detallar qué debe prepararse o despacharse (ej: “CMF 1.5 + INSTRUMENTAL”)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? DispatchInstruction { get; set; }
|
public string? DispatchInstruction { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cantidad de impresiones
|
/// Cantidad de impresiones
|
||||||
@ -104,7 +107,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Observaciones internas
|
/// Observaciones internas
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Observations { get; set; }
|
public string? Observations { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fecha de creación
|
/// Fecha de creación
|
||||||
@ -115,13 +118,9 @@
|
|||||||
/// Fecha de modificación
|
/// Fecha de modificación
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? Modifiedat { get; set; }
|
public DateTime? Modifiedat { get; set; }
|
||||||
|
|
||||||
public virtual ICollection<EQuoteAdjustment> PhSQuoteAdjustments { get; set; } = new List<EQuoteAdjustment>();
|
public virtual ICollection<EQuoteAdjustment> PhSQuoteAdjustments { get; set; } = new List<EQuoteAdjustment>();
|
||||||
|
|
||||||
public virtual ICollection<EQuoteDetail> PhSQuoteDetails { get; set; } = new List<EQuoteDetail>();
|
public virtual ICollection<EQuoteDetail> PhSQuoteDetails { get; set; } = new List<EQuoteDetail>();
|
||||||
|
|
||||||
public virtual ICollection<EQuoteRole> PhSQuoteRoles { get; set; } = new List<EQuoteRole>();
|
public virtual ICollection<EQuoteRole> PhSQuoteRoles { get; set; } = new List<EQuoteRole>();
|
||||||
|
|
||||||
public virtual ICollection<EQuoteTax> PhSQuoteTaxes { get; set; } = new List<EQuoteTax>();
|
public virtual ICollection<EQuoteTax> PhSQuoteTaxes { get; set; } = new List<EQuoteTax>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ namespace Domain.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tipo de entidad asociada (Ej: PhS_Professionals, PhS_Institutions, PhS_Patients)
|
/// Tipo de entidad asociada (Ej: PhS_Professionals, PhS_Institutions, PhS_Patients)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Entitytype { get; set; } = null!;
|
public string Entitytype { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ID de la entidad asociada
|
/// ID de la entidad asociada
|
||||||
|
|||||||
@ -15,12 +15,12 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Nombre descriptivo del impuesto
|
/// Nombre descriptivo del impuesto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Taxname { get; set; } = null!;
|
public string Taxname { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Código o identificador oficial del impuesto (ej. AFIP)
|
/// Código o identificador oficial del impuesto (ej. AFIP)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Taxcode { get; set; }
|
public string? Taxcode { get; set; } = String.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base imponible del impuesto (importe gravado)
|
/// Base imponible del impuesto (importe gravado)
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
namespace Models.Interfaces
|
using Models.Models;
|
||||||
|
|
||||||
|
namespace Models.Interfaces
|
||||||
{
|
{
|
||||||
public interface IPhSFormSeriesRepository
|
public interface IPhSFormSeriesRepository
|
||||||
{
|
{
|
||||||
|
Task<PhSFormSeries?> GetByIdAsync(int formSeriesId);
|
||||||
Task<int> GetNextInternalNumberAsync(int formSeriesId);
|
Task<int> GetNextInternalNumberAsync(int formSeriesId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using Domain.Entities;
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
|
|
||||||
namespace Models.Interfaces
|
namespace Models.Interfaces
|
||||||
{
|
{
|
||||||
@ -12,36 +11,34 @@ namespace Models.Interfaces
|
|||||||
Task<PagedResult<EQuoteHeader>> SearchAsync(int? customerId, string? quoteNumber, int? professionalId,
|
Task<PagedResult<EQuoteHeader>> SearchAsync(int? customerId, string? quoteNumber, int? professionalId,
|
||||||
int? institutionId, int? patientId, DateTime? issueDateFrom, DateTime? issueDateTo, string? status,
|
int? institutionId, int? patientId, DateTime? issueDateFrom, DateTime? issueDateTo, string? status,
|
||||||
int page = 1, int pageSize = 50);
|
int page = 1, int pageSize = 50);
|
||||||
|
|
||||||
Task<EQuoteHeader> AddAsync(EQuoteHeader quoteHeader);
|
|
||||||
Task UpdateAsync(EQuoteHeader quoteHeader);
|
Task UpdateAsync(EQuoteHeader quoteHeader);
|
||||||
Task DeleteAsync(int id);
|
Task DeleteAsync(int id);
|
||||||
|
|
||||||
// Ajustes
|
// Ajustes
|
||||||
Task<IEnumerable<EQuoteAdjustment>> GetAdjustmentsByQuoteIdAsync(int quoteId);
|
//Task<IEnumerable<EQuoteAdjustment>> GetAdjustmentsByQuoteIdAsync(int quoteId);
|
||||||
Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment);
|
//Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment);
|
||||||
Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment);
|
//Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment);
|
||||||
Task DeleteAdjustmentAsync(int adjustmentId);
|
//Task DeleteAdjustmentAsync(int adjustmentId);
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Obtiene todos los impuestos asociados a un presupuesto dado por su ID.
|
///// Obtiene todos los impuestos asociados a un presupuesto dado por su ID.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId);
|
//Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId);
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Agrega un nuevo impuesto al presupuesto correspondiente.
|
///// Agrega un nuevo impuesto al presupuesto correspondiente.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
Task<EQuoteTax> AddTaxAsync(EQuoteTax tax);
|
//Task<EQuoteTax> AddTaxAsync(EQuoteTax tax);
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Actualiza los datos de un impuesto existente en un presupuesto.
|
///// Actualiza los datos de un impuesto existente en un presupuesto.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
Task UpdateTaxAsync(EQuoteTax tax);
|
//Task UpdateTaxAsync(EQuoteTax tax);
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Elimina un impuesto asociado a un presupuesto a partir de su ID.
|
///// Elimina un impuesto asociado a un presupuesto a partir de su ID.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
Task DeleteTaxAsync(int taxId);
|
//Task DeleteTaxAsync(int taxId);
|
||||||
Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
//Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||||
Task<IDbContextTransaction> BeginTransactionAsync();
|
//Task<IDbContextTransaction> BeginTransactionAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
Models/Interfaces/IPhSQuoteRepository.cs
Normal file
9
Models/Interfaces/IPhSQuoteRepository.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
|
||||||
|
namespace Models.Interfaces
|
||||||
|
{
|
||||||
|
public interface IPhSQuoteRepository
|
||||||
|
{
|
||||||
|
Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,7 +29,12 @@ namespace PhronCare.Core.Data.Repositories.Sales
|
|||||||
|
|
||||||
return (int)nextNumberParam.Value;
|
return (int)nextNumberParam.Value;
|
||||||
}
|
}
|
||||||
|
public async Task<PhSFormSeries?> GetByIdAsync(int formSeriesId)
|
||||||
|
{
|
||||||
|
return await _context.PhSFormSeries
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(s => s.Id == formSeriesId);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
using Domain.Entities;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Domain.Generics;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
using Models.Helpers;
|
|
||||||
using Models.Interfaces;
|
using Models.Interfaces;
|
||||||
|
using Models.Helpers;
|
||||||
using Models.Models;
|
using Models.Models;
|
||||||
|
using Domain.Entities;
|
||||||
|
using Domain.Generics;
|
||||||
|
|
||||||
namespace PhronCare.Core.Data.Repositories.Sales
|
namespace PhronCare.Core.Data.Repositories.Sales
|
||||||
{
|
{
|
||||||
public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context, IPhSFormSeriesRepository formSeriesRepository) : IPhSQuoteHeaderRepository
|
public class PhSQuoteHeaderRepository(PhronCareOperationsHubContext context) : IPhSQuoteHeaderRepository
|
||||||
{
|
{
|
||||||
private readonly PhronCareOperationsHubContext _context = context;
|
private readonly PhronCareOperationsHubContext _context = context;
|
||||||
private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
|
//private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
|
||||||
|
#region Metodos
|
||||||
public async Task<PagedResult<EQuoteHeader>> GetAllAsync(int page = 1, int pageSize = 50)
|
public async Task<PagedResult<EQuoteHeader>> GetAllAsync(int page = 1, int pageSize = 50)
|
||||||
{
|
{
|
||||||
var query = _context.PhSQuoteHeaders
|
var query = _context.PhSQuoteHeaders
|
||||||
@ -100,13 +100,7 @@ namespace PhronCare.Core.Data.Repositories.Sales
|
|||||||
PageSize = pagedEntities.PageSize
|
PageSize = pagedEntities.PageSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public async Task<EQuoteHeader> AddAsync(EQuoteHeader quoteHeader)
|
|
||||||
{
|
|
||||||
var dbEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quoteHeader);
|
|
||||||
_context.PhSQuoteHeaders.Add(dbEntity);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
return EntityMapper.MapEntity<PhSQuoteHeader, EQuoteHeader>(dbEntity);
|
|
||||||
}
|
|
||||||
public async Task UpdateAsync(EQuoteHeader quoteHeader)
|
public async Task UpdateAsync(EQuoteHeader quoteHeader)
|
||||||
{
|
{
|
||||||
var dbEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quoteHeader);
|
var dbEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quoteHeader);
|
||||||
@ -122,172 +116,173 @@ namespace PhronCare.Core.Data.Repositories.Sales
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
#region <DECRECATED>
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// Métodos para Ajustes
|
// Métodos para Ajustes
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
public async Task<IEnumerable<EQuoteAdjustment>> GetAdjustmentsByQuoteIdAsync(int quoteId)
|
//public async Task<IEnumerable<EQuoteAdjustment>> GetAdjustmentsByQuoteIdAsync(int quoteId)
|
||||||
{
|
//{
|
||||||
var adjustments = await _context.PhSQuoteAdjustments
|
// var adjustments = await _context.PhSQuoteAdjustments
|
||||||
.Where(a => a.QuoteheaderId == quoteId)
|
// .Where(a => a.QuoteheaderId == quoteId)
|
||||||
.ToListAsync();
|
// .ToListAsync();
|
||||||
|
|
||||||
return adjustments.Select(EntityMapper.MapEntity<PhSQuoteAdjustment, EQuoteAdjustment>);
|
// return adjustments.Select(EntityMapper.MapEntity<PhSQuoteAdjustment, EQuoteAdjustment>);
|
||||||
}
|
//}
|
||||||
public async Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment)
|
//public async Task<EQuoteAdjustment> AddAdjustmentAsync(EQuoteAdjustment adjustment)
|
||||||
{
|
//{
|
||||||
var dbEntity = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adjustment);
|
// var dbEntity = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adjustment);
|
||||||
_context.PhSQuoteAdjustments.Add(dbEntity);
|
// _context.PhSQuoteAdjustments.Add(dbEntity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
return EntityMapper.MapEntity<PhSQuoteAdjustment, EQuoteAdjustment>(dbEntity);
|
// return EntityMapper.MapEntity<PhSQuoteAdjustment, EQuoteAdjustment>(dbEntity);
|
||||||
}
|
//}
|
||||||
public async Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment)
|
//public async Task UpdateAdjustmentAsync(EQuoteAdjustment adjustment)
|
||||||
{
|
//{
|
||||||
var dbEntity = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adjustment);
|
// var dbEntity = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adjustment);
|
||||||
_context.PhSQuoteAdjustments.Update(dbEntity);
|
// _context.PhSQuoteAdjustments.Update(dbEntity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
}
|
//}
|
||||||
public async Task DeleteAdjustmentAsync(int adjustmentId)
|
//public async Task DeleteAdjustmentAsync(int adjustmentId)
|
||||||
{
|
//{
|
||||||
var entity = await _context.PhSQuoteAdjustments.FindAsync(adjustmentId);
|
// var entity = await _context.PhSQuoteAdjustments.FindAsync(adjustmentId);
|
||||||
if (entity != null)
|
// if (entity != null)
|
||||||
{
|
// {
|
||||||
_context.PhSQuoteAdjustments.Remove(entity);
|
// _context.PhSQuoteAdjustments.Remove(entity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// ----------------------------
|
//// ----------------------------
|
||||||
// Métodos para Impuestos
|
//// Métodos para Impuestos
|
||||||
// ----------------------------
|
//// ----------------------------
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Obtiene todos los impuestos asociados a un presupuesto dado por su ID.
|
///// Obtiene todos los impuestos asociados a un presupuesto dado por su ID.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
public async Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId)
|
//public async Task<IEnumerable<EQuoteTax>> GetTaxesByQuoteIdAsync(int quoteId)
|
||||||
{
|
//{
|
||||||
var taxes = await _context.PhSQuoteTaxes
|
// var taxes = await _context.PhSQuoteTaxes
|
||||||
.Where(t => t.QuoteheaderId == quoteId)
|
// .Where(t => t.QuoteheaderId == quoteId)
|
||||||
.ToListAsync();
|
// .ToListAsync();
|
||||||
|
|
||||||
return taxes.Select(EntityMapper.MapEntity<PhSQuoteTaxis, EQuoteTax>);
|
// return taxes.Select(EntityMapper.MapEntity<PhSQuoteTaxis, EQuoteTax>);
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Agrega un nuevo impuesto al presupuesto correspondiente.
|
///// Agrega un nuevo impuesto al presupuesto correspondiente.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
public async Task<EQuoteTax> AddTaxAsync(EQuoteTax tax)
|
//public async Task<EQuoteTax> AddTaxAsync(EQuoteTax tax)
|
||||||
{
|
//{
|
||||||
var dbEntity = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
// var dbEntity = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
||||||
_context.PhSQuoteTaxes.Add(dbEntity);
|
// _context.PhSQuoteTaxes.Add(dbEntity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
return EntityMapper.MapEntity<PhSQuoteTaxis, EQuoteTax>(dbEntity);
|
// return EntityMapper.MapEntity<PhSQuoteTaxis, EQuoteTax>(dbEntity);
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Actualiza los datos de un impuesto existente en un presupuesto.
|
///// Actualiza los datos de un impuesto existente en un presupuesto.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
public async Task UpdateTaxAsync(EQuoteTax tax)
|
//public async Task UpdateTaxAsync(EQuoteTax tax)
|
||||||
{
|
//{
|
||||||
var dbEntity = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
// var dbEntity = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
||||||
_context.PhSQuoteTaxes.Update(dbEntity);
|
// _context.PhSQuoteTaxes.Update(dbEntity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Elimina un impuesto asociado a un presupuesto a partir de su ID.
|
///// Elimina un impuesto asociado a un presupuesto a partir de su ID.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
public async Task DeleteTaxAsync(int taxId)
|
//public async Task DeleteTaxAsync(int taxId)
|
||||||
{
|
//{
|
||||||
var entity = await _context.PhSQuoteTaxes.FindAsync(taxId);
|
// var entity = await _context.PhSQuoteTaxes.FindAsync(taxId);
|
||||||
if (entity != null)
|
// if (entity != null)
|
||||||
{
|
// {
|
||||||
_context.PhSQuoteTaxes.Remove(entity);
|
// _context.PhSQuoteTaxes.Remove(entity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
//#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Crea un nuevo presupuesto, incluyendo encabezado, detalles, roles, ajustes e impuestos asociados.
|
///// Crea un nuevo presupuesto, incluyendo encabezado, detalles, roles, ajustes e impuestos asociados.
|
||||||
/// Genera automáticamente el número de presupuesto en base a la serie indicada.
|
///// Genera automáticamente el número de presupuesto en base a la serie indicada.
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <param name="quote">Presupuesto a registrar, incluyendo entidades relacionadas.</param>
|
///// <param name="quote">Presupuesto a registrar, incluyendo entidades relacionadas.</param>
|
||||||
/// <param name="formSeriesId">Identificador de la serie de numeración a utilizar.</param>
|
///// <param name="formSeriesId">Identificador de la serie de numeración a utilizar.</param>
|
||||||
/// <returns>Cadena con el número generado del presupuesto.</returns>
|
///// <returns>Cadena con el número generado del presupuesto.</returns>
|
||||||
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
//public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
||||||
{
|
//{
|
||||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
// using var transaction = await _context.Database.BeginTransactionAsync();
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
// Obtener el próximo número de presupuesto desde SP
|
// // Obtener el próximo número de presupuesto desde SP
|
||||||
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
|
// var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
|
||||||
quote.Quotenumber = nextNumber.ToString();
|
// quote.Quotenumber = nextNumber.ToString();
|
||||||
|
|
||||||
// Map y guardado de Header
|
// // Map y guardado de Header
|
||||||
var headerEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quote);
|
// var headerEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quote);
|
||||||
_context.PhSQuoteHeaders.Add(headerEntity);
|
// _context.PhSQuoteHeaders.Add(headerEntity);
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
|
|
||||||
// Guardado de Detalles
|
// // Guardado de Detalles
|
||||||
if (quote.PhSQuoteDetails?.Any() == true)
|
// if (quote.PhSQuoteDetails?.Any() == true)
|
||||||
{
|
// {
|
||||||
foreach (var detail in quote.PhSQuoteDetails)
|
// foreach (var detail in quote.PhSQuoteDetails)
|
||||||
{
|
// {
|
||||||
detail.QuoteheaderId = headerEntity.Id;
|
// detail.QuoteheaderId = headerEntity.Id;
|
||||||
var dbDetail = EntityMapper.MapEntity<EQuoteDetail, PhSQuoteDetail>(detail);
|
// var dbDetail = EntityMapper.MapEntity<EQuoteDetail, PhSQuoteDetail>(detail);
|
||||||
_context.PhSQuoteDetails.Add(dbDetail);
|
// _context.PhSQuoteDetails.Add(dbDetail);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Guardado de Roles
|
// // Guardado de Roles
|
||||||
if (quote.PhSQuoteRoles?.Any() == true)
|
// if (quote.PhSQuoteRoles?.Any() == true)
|
||||||
{
|
// {
|
||||||
foreach (var role in quote.PhSQuoteRoles)
|
// foreach (var role in quote.PhSQuoteRoles)
|
||||||
{
|
// {
|
||||||
role.QuoteheaderId = headerEntity.Id;
|
// role.QuoteheaderId = headerEntity.Id;
|
||||||
var dbRole = EntityMapper.MapEntity<EQuoteRole, PhSQuoteRole>(role);
|
// var dbRole = EntityMapper.MapEntity<EQuoteRole, PhSQuoteRole>(role);
|
||||||
_context.PhSQuoteRoles.Add(dbRole);
|
// _context.PhSQuoteRoles.Add(dbRole);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Guardado de Ajustes
|
// // Guardado de Ajustes
|
||||||
if (quote.PhSQuoteAdjustments?.Any() == true)
|
// if (quote.PhSQuoteAdjustments?.Any() == true)
|
||||||
{
|
// {
|
||||||
foreach (var adj in quote.PhSQuoteAdjustments)
|
// foreach (var adj in quote.PhSQuoteAdjustments)
|
||||||
{
|
// {
|
||||||
adj.QuoteheaderId = headerEntity.Id;
|
// adj.QuoteheaderId = headerEntity.Id;
|
||||||
var dbAdj = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adj);
|
// var dbAdj = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adj);
|
||||||
_context.PhSQuoteAdjustments.Add(dbAdj);
|
// _context.PhSQuoteAdjustments.Add(dbAdj);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Guardado de Impuestos
|
// // Guardado de Impuestos
|
||||||
if (quote.PhSQuoteTaxes?.Any() == true)
|
// if (quote.PhSQuoteTaxes?.Any() == true)
|
||||||
{
|
// {
|
||||||
foreach (var tax in quote.PhSQuoteTaxes)
|
// foreach (var tax in quote.PhSQuoteTaxes)
|
||||||
{
|
// {
|
||||||
tax.QuoteheaderId = headerEntity.Id;
|
// tax.QuoteheaderId = headerEntity.Id;
|
||||||
var dbTax = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
// var dbTax = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
||||||
_context.PhSQuoteTaxes.Add(dbTax);
|
// _context.PhSQuoteTaxes.Add(dbTax);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
// await _context.SaveChangesAsync();
|
||||||
await transaction.CommitAsync();
|
// await transaction.CommitAsync();
|
||||||
|
|
||||||
return headerEntity.Quotenumber;
|
// return headerEntity.Quotenumber;
|
||||||
}
|
// }
|
||||||
catch
|
// catch
|
||||||
{
|
// {
|
||||||
await transaction.RollbackAsync();
|
// await transaction.RollbackAsync();
|
||||||
throw;
|
// throw;
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
public async Task<IDbContextTransaction> BeginTransactionAsync()
|
//public async Task<IDbContextTransaction> BeginTransactionAsync()
|
||||||
{
|
//{
|
||||||
return await _context.Database.BeginTransactionAsync();
|
// return await _context.Database.BeginTransactionAsync();
|
||||||
}
|
//}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
95
Models/Repositories/PhSQuoteRepository.cs
Normal file
95
Models/Repositories/PhSQuoteRepository.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
using Models.Helpers;
|
||||||
|
using Models.Interfaces;
|
||||||
|
using Models.Models;
|
||||||
|
|
||||||
|
namespace Models.Repositories
|
||||||
|
{
|
||||||
|
public class PhSQuoteRepository(PhronCareOperationsHubContext context,
|
||||||
|
IPhSFormSeriesRepository formSeriesRepository) : IPhSQuoteRepository
|
||||||
|
{
|
||||||
|
private readonly PhronCareOperationsHubContext _context = context;
|
||||||
|
private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
|
||||||
|
|
||||||
|
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un nuevo presupuesto, incluyendo encabezado, detalles, roles, ajustes e impuestos asociados.
|
||||||
|
/// Genera automáticamente el número de presupuesto en base a la serie indicada.
|
||||||
|
/// <param name="quote">Presupuesto a registrar, incluyendo entidades relacionadas.</param>
|
||||||
|
/// <param name="formSeriesId">Identificador de la serie de numeración a utilizar.</param>
|
||||||
|
/// <returns>Cadena con el número generado del presupuesto.</returns>
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
||||||
|
{
|
||||||
|
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
|
||||||
|
var series = await _formSeriesRepository.GetByIdAsync(formSeriesId)
|
||||||
|
?? throw new InvalidOperationException("Serie no encontrada");
|
||||||
|
int padding = 8; /* format "00000000" */
|
||||||
|
|
||||||
|
quote.Quotenumber = $"{series.Letter}-{nextNumber.ToString($"D{padding}")}";
|
||||||
|
|
||||||
|
var headerEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quote);
|
||||||
|
_context.PhSQuoteHeaders.Add(headerEntity);
|
||||||
|
|
||||||
|
#region Nota: Esta seccion queda para futura modificacion en caso de cambiar CASCADE INSERT de las entidades relacionadas
|
||||||
|
//// Guardado de Detalles
|
||||||
|
//if (quote.PhSQuoteDetails?.Any() == true)
|
||||||
|
//{
|
||||||
|
// foreach (var detail in quote.PhSQuoteDetails)
|
||||||
|
// {
|
||||||
|
// detail.QuoteheaderId = headerEntity.Id;
|
||||||
|
// var dbDetail = EntityMapper.MapEntity<EQuoteDetail, PhSQuoteDetail>(detail);
|
||||||
|
// _context.PhSQuoteDetails.Add(dbDetail);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// Guardado de Roles
|
||||||
|
//if (quote.PhSQuoteRoles?.Any() == true)
|
||||||
|
//{
|
||||||
|
// foreach (var role in quote.PhSQuoteRoles)
|
||||||
|
// {
|
||||||
|
// role.QuoteheaderId = headerEntity.Id;
|
||||||
|
// var dbRole = EntityMapper.MapEntity<EQuoteRole, PhSQuoteRole>(role);
|
||||||
|
// _context.PhSQuoteRoles.Add(dbRole);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// Guardado de Ajustes
|
||||||
|
//if (quote.PhSQuoteAdjustments?.Any() == true)
|
||||||
|
//{
|
||||||
|
// foreach (var adj in quote.PhSQuoteAdjustments)
|
||||||
|
// {
|
||||||
|
// adj.QuoteheaderId = headerEntity.Id;
|
||||||
|
// var dbAdj = EntityMapper.MapEntity<EQuoteAdjustment, PhSQuoteAdjustment>(adj);
|
||||||
|
// _context.PhSQuoteAdjustments.Add(dbAdj);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// Guardado de Impuestos
|
||||||
|
//if (quote.PhSQuoteTaxes?.Any() == true)
|
||||||
|
//{
|
||||||
|
// foreach (var tax in quote.PhSQuoteTaxes)
|
||||||
|
// {
|
||||||
|
// tax.QuoteheaderId = headerEntity.Id;
|
||||||
|
// var dbTax = EntityMapper.MapEntity<EQuoteTax, PhSQuoteTaxis>(tax);
|
||||||
|
// _context.PhSQuoteTaxes.Add(dbTax);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
return headerEntity.Quotenumber;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -91,32 +91,6 @@ namespace phronCare.API.Controllers.Sales
|
|||||||
|
|
||||||
#region Crear / Actualizar / Eliminar
|
#region Crear / Actualizar / Eliminar
|
||||||
|
|
||||||
[HttpPost("create")]
|
|
||||||
public async Task<IActionResult> Create([FromBody] EQuoteHeader quote, [FromQuery] int formSeriesId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (quote == null)
|
|
||||||
return BadRequest("El presupuesto no puede ser nulo.");
|
|
||||||
|
|
||||||
var result = await _quoteService.CreateQuoteAsync(quote, formSeriesId);
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
catch (ArgumentNullException ex)
|
|
||||||
{
|
|
||||||
return BadRequest($"Validación fallida: {ex.Message}");
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
return BadRequest($"Error de negocio: {ex.Message}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
|
||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("update")]
|
[HttpPut("update")]
|
||||||
public async Task<IActionResult> Update([FromBody] EQuoteHeader quote)
|
public async Task<IActionResult> Update([FromBody] EQuoteHeader quote)
|
||||||
{
|
{
|
||||||
@ -152,145 +126,6 @@ namespace phronCare.API.Controllers.Sales
|
|||||||
|
|
||||||
#endregion
|
#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
|
|
||||||
|
|
||||||
#region Ajustes Comerciales (QuoteAdjustments)
|
|
||||||
|
|
||||||
[HttpGet("{quoteId:int}/adjustments")]
|
|
||||||
public async Task<IActionResult> GetAdjustments(int quoteId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var adjustments = await _quoteService.GetAdjustmentsByQuoteIdAsync(quoteId);
|
|
||||||
return Ok(adjustments);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
|
||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("{quoteId:int}/adjustments")]
|
|
||||||
public async Task<IActionResult> AddAdjustment(int quoteId, [FromBody] EQuoteAdjustment adjustment)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (adjustment == null || quoteId != adjustment.QuoteheaderId)
|
|
||||||
return BadRequest("Datos inválidos para el ajuste.");
|
|
||||||
|
|
||||||
var result = await _quoteService.AddAdjustmentAsync(adjustment);
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
|
||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("adjustments")]
|
|
||||||
public async Task<IActionResult> UpdateAdjustment([FromBody] EQuoteAdjustment adjustment)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (adjustment == null || adjustment.Id <= 0)
|
|
||||||
return BadRequest("Datos inválidos para actualizar el ajuste.");
|
|
||||||
|
|
||||||
await _quoteService.UpdateAdjustmentAsync(adjustment);
|
|
||||||
return Ok("Ajuste actualizado correctamente.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
|
||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("adjustments/{adjustmentId:int}")]
|
|
||||||
public async Task<IActionResult> DeleteAdjustment(int adjustmentId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _quoteService.DeleteAdjustmentAsync(adjustmentId);
|
|
||||||
return Ok("Ajuste eliminado correctamente.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
|
||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Exportación
|
#region Exportación
|
||||||
|
|
||||||
@ -314,15 +149,23 @@ namespace phronCare.API.Controllers.Sales
|
|||||||
|
|
||||||
#region Endpoint de emision de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
#region Endpoint de emision de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
||||||
[HttpPost("createfull")]
|
[HttpPost("createfull")]
|
||||||
public async Task<IActionResult> CreateFullQuote([FromBody] EQuoteHeader quote, [FromQuery] int formSeriesId)
|
public async Task<IActionResult> CreateFullQuote([FromBody] CreateFullQuoteRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (quote == null)
|
// Validamos que el request y el objeto Quote no sean nulos
|
||||||
return BadRequest("El presupuesto no puede ser nulo.");
|
if (request == null || request.Quote == null)
|
||||||
|
return BadRequest("El payload no puede contener elementos nulos.");
|
||||||
|
|
||||||
|
// Desempaquetamos los datos
|
||||||
|
var quote = request.Quote;
|
||||||
|
var formSeriesId = request.FormSeriesId;
|
||||||
|
|
||||||
|
// Llamada al servicio de negocio
|
||||||
var quoteNumber = await _quoteService.CreateFullQuoteAsync(quote, formSeriesId);
|
var quoteNumber = await _quoteService.CreateFullQuoteAsync(quote, formSeriesId);
|
||||||
return Ok(new { QuoteNumber = quoteNumber });
|
|
||||||
|
// Devolvemos el número generado
|
||||||
|
return Ok(new { Success = true, QuoteNumber = quoteNumber });
|
||||||
}
|
}
|
||||||
catch (ArgumentNullException ex)
|
catch (ArgumentNullException ex)
|
||||||
{
|
{
|
||||||
@ -334,10 +177,16 @@ namespace phronCare.API.Controllers.Sales
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
return StatusCode(500, $"Ocurrió un error interno: {ex.Message}");
|
||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CreateFullQuoteRequest
|
||||||
|
{
|
||||||
|
public EQuoteHeader Quote { get; set; } = default!;
|
||||||
|
public int FormSeriesId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,8 +244,8 @@ static void RepositorysAndServices(WebApplicationBuilder builder)
|
|||||||
builder.Services.AddScoped<IQuoteDom, QuoteService>();
|
builder.Services.AddScoped<IQuoteDom, QuoteService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<IPhSQuoteHeaderRepository, PhSQuoteHeaderRepository>();
|
builder.Services.AddScoped<IPhSQuoteHeaderRepository, PhSQuoteHeaderRepository>();
|
||||||
builder.Services.AddScoped<IPhSQuoteDetailRepository, PhSQuoteDetailRepository>();
|
builder.Services.AddScoped<IPhSQuoteRepository, PhSQuoteRepository>();
|
||||||
builder.Services.AddScoped<IPhSQuoteRoleRepository, PhSQuoteRoleRepository>();
|
//builder.Services.AddScoped<IPhSQuoteRoleRepository, PhSQuoteRoleRepository>();
|
||||||
builder.Services.AddScoped<IPhSFormSeriesRepository, PhSFormSeriesRepository>();
|
builder.Services.AddScoped<IPhSFormSeriesRepository, PhSFormSeriesRepository>();
|
||||||
|
|
||||||
// Registrar el service de lookup
|
// Registrar el service de lookup
|
||||||
|
|||||||
@ -1556,112 +1556,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
|
||||||
"Method": "GetAdjustments",
|
|
||||||
"RelativePath": "api/Quote/{quoteId}/adjustments",
|
|
||||||
"HttpMethod": "GET",
|
|
||||||
"IsController": true,
|
|
||||||
"Order": 0,
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "quoteId",
|
|
||||||
"Type": "System.Int32",
|
|
||||||
"IsRequired": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnTypes": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
|
||||||
"Method": "AddAdjustment",
|
|
||||||
"RelativePath": "api/Quote/{quoteId}/adjustments",
|
|
||||||
"HttpMethod": "POST",
|
|
||||||
"IsController": true,
|
|
||||||
"Order": 0,
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "quoteId",
|
|
||||||
"Type": "System.Int32",
|
|
||||||
"IsRequired": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "adjustment",
|
|
||||||
"Type": "Domain.Entities.EQuoteAdjustment",
|
|
||||||
"IsRequired": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnTypes": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "UpdateAdjustment",
|
|
||||||
"RelativePath": "api/Quote/adjustments",
|
|
||||||
"HttpMethod": "PUT",
|
|
||||||
"IsController": true,
|
|
||||||
"Order": 0,
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "adjustment",
|
|
||||||
"Type": "Domain.Entities.EQuoteAdjustment",
|
|
||||||
"IsRequired": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnTypes": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
|
||||||
"Method": "DeleteAdjustment",
|
|
||||||
"RelativePath": "api/Quote/adjustments/{adjustmentId}",
|
|
||||||
"HttpMethod": "DELETE",
|
|
||||||
"IsController": true,
|
|
||||||
"Order": 0,
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "adjustmentId",
|
|
||||||
"Type": "System.Int32",
|
|
||||||
"IsRequired": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnTypes": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||||
"Method": "GetAll",
|
"Method": "GetAll",
|
||||||
@ -1683,27 +1577,6 @@
|
|||||||
],
|
],
|
||||||
"ReturnTypes": []
|
"ReturnTypes": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
|
||||||
"Method": "Create",
|
|
||||||
"RelativePath": "api/Quote/create",
|
|
||||||
"HttpMethod": "POST",
|
|
||||||
"IsController": true,
|
|
||||||
"Order": 0,
|
|
||||||
"Parameters": [
|
|
||||||
{
|
|
||||||
"Name": "quote",
|
|
||||||
"Type": "Domain.Entities.EQuoteHeader",
|
|
||||||
"IsRequired": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "formSeriesId",
|
|
||||||
"Type": "System.Int32",
|
|
||||||
"IsRequired": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ReturnTypes": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||||
"Method": "CreateFullQuote",
|
"Method": "CreateFullQuote",
|
||||||
@ -1713,14 +1586,9 @@
|
|||||||
"Order": 0,
|
"Order": 0,
|
||||||
"Parameters": [
|
"Parameters": [
|
||||||
{
|
{
|
||||||
"Name": "quote",
|
"Name": "request",
|
||||||
"Type": "Domain.Entities.EQuoteHeader",
|
"Type": "phronCare.API.Controllers.Sales.QuoteController\u002BCreateFullQuoteRequest",
|
||||||
"IsRequired": true
|
"IsRequired": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "formSeriesId",
|
|
||||||
"Type": "System.Int32",
|
|
||||||
"IsRequired": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ReturnTypes": []
|
"ReturnTypes": []
|
||||||
@ -1818,38 +1686,6 @@
|
|||||||
],
|
],
|
||||||
"ReturnTypes": []
|
"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",
|
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||||
"Method": "Update",
|
"Method": "Update",
|
||||||
|
|||||||
@ -4,7 +4,11 @@
|
|||||||
@using Blazored.Typeahead
|
@using Blazored.Typeahead
|
||||||
@using Services.Lookups
|
@using Services.Lookups
|
||||||
@using phronCare.UIBlazor.Pages.Sales.Modals
|
@using phronCare.UIBlazor.Pages.Sales.Modals
|
||||||
|
@using phronCare.UIBlazor.Services.Sales.Quotes
|
||||||
@inject ISalesLookupService SalesLookupService
|
@inject ISalesLookupService SalesLookupService
|
||||||
|
@inject IQuoteService QuoteService
|
||||||
|
@inject IToastService toastService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
@inject IModalService Modal
|
@inject IModalService Modal
|
||||||
|
|
||||||
<EditForm Model="_quoteModel" >
|
<EditForm Model="_quoteModel" >
|
||||||
@ -110,9 +114,11 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label">Moneda</label>
|
<label class="form-label">Moneda</label>
|
||||||
<InputSelect class="form-select" @bind-Value="_quoteModel.Currency">
|
<InputSelect class="form-select" @bind-Value="_quoteModel.Currency">
|
||||||
|
<option value="">-- Seleccionar moneda --</option>
|
||||||
<option value="ARS">ARS</option>
|
<option value="ARS">ARS</option>
|
||||||
<option value="USD">USD</option>
|
<option value="USD">USD</option>
|
||||||
</InputSelect>
|
</InputSelect>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label">Tipo de cambio</label>
|
<label class="form-label">Tipo de cambio</label>
|
||||||
@ -296,7 +302,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-end">
|
<div class="card-footer text-end">
|
||||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
<button type="button" class="btn btn-primary" @onclick="HandleValidSubmit">Guardar</button>
|
||||||
<button type="button" class="btn btn-secondary ms-2">Cancelar</button>
|
<button type="button" class="btn btn-secondary ms-2">Cancelar</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -369,12 +375,12 @@
|
|||||||
=> SetLookupSelection(item, sel => _selectedCustomer = sel, id => _quoteModel.CustomerId = id);
|
=> SetLookupSelection(item, sel => _selectedCustomer = sel, id => _quoteModel.CustomerId = id);
|
||||||
private Task OnPersonSelected(ELookUpItem item)
|
private Task OnPersonSelected(ELookUpItem item)
|
||||||
=> SetLookupSelection(item, sel => _selectedPerson = sel, id => _quoteModel.PeopleId = id);
|
=> SetLookupSelection(item, sel => _selectedPerson = sel, id => _quoteModel.PeopleId = id);
|
||||||
private Task OnProfessionalSelected(ELookUpItem item)
|
// private Task OnProfessionalSelected(ELookUpItem item)
|
||||||
=> SetLookupSelection(item, sel => _selectedProfessional = sel);
|
// => SetLookupSelection(item, sel => _selectedProfessional = sel);
|
||||||
private Task OnInstitutionSelected(ELookUpItem item)
|
// private Task OnInstitutionSelected(ELookUpItem item)
|
||||||
=> SetLookupSelection(item, sel => _selectedInstitution = sel);
|
// => SetLookupSelection(item, sel => _selectedInstitution = sel);
|
||||||
private Task OnPatientSelected(ELookUpItem item)
|
// private Task OnPatientSelected(ELookUpItem item)
|
||||||
=> SetLookupSelection(item, sel => _selectedPatient = sel);
|
// => SetLookupSelection(item, sel => _selectedPatient = sel);
|
||||||
private Task SetLookupSelection(ELookUpItem? item, Action<ELookUpItem?> setSelected, Action<int>? setModelId = null)
|
private Task SetLookupSelection(ELookUpItem? item, Action<ELookUpItem?> setSelected, Action<int>? setModelId = null)
|
||||||
{
|
{
|
||||||
setSelected(item);
|
setSelected(item);
|
||||||
@ -439,4 +445,65 @@
|
|||||||
_quoteModel.PhSQuoteTaxes.Remove(tax);
|
_quoteModel.PhSQuoteTaxes.Remove(tax);
|
||||||
RecalculateTotals();
|
RecalculateTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleValidSubmit()
|
||||||
|
{
|
||||||
|
// Si necesitas validar algo extra antes de llamar al servicio, hazlo aquí
|
||||||
|
int selectedSeriesId = 1/* obtenlo de donde corresponda, p.ej. de un dropdown */;
|
||||||
|
|
||||||
|
var result = await QuoteService.CreateFullQuoteAsync(_quoteModel, selectedSeriesId);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
toastService.ShowError(result.ErrorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toastService.ShowSuccess($"Presupuesto creado: {result.QuoteNumber}");
|
||||||
|
//Navigation.NavigateTo($"/sales/quotes/details/{result.QuoteNumber}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Agrega o actualiza un rol dentro de _quoteModel.PhSQuoteRoles
|
||||||
|
/// </summary>
|
||||||
|
private void AddOrUpdateRole(string entityType, int entityId, string roleName)
|
||||||
|
{
|
||||||
|
var existing = _quoteModel.PhSQuoteRoles
|
||||||
|
.FirstOrDefault(r => r.Entitytype == entityType);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.EntityId = entityId;
|
||||||
|
existing.Role = roleName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_quoteModel.PhSQuoteRoles.Add(new EQuoteRole
|
||||||
|
{
|
||||||
|
// QuoteheaderId lo llenará EF en el servidor
|
||||||
|
Entitytype = entityType,
|
||||||
|
EntityId = entityId,
|
||||||
|
Role = roleName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Task OnProfessionalSelected(ELookUpItem item)
|
||||||
|
{
|
||||||
|
_selectedProfessional = item;
|
||||||
|
AddOrUpdateRole("PhS_Professionals", item.Id, "Medico");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task OnInstitutionSelected(ELookUpItem item)
|
||||||
|
{
|
||||||
|
_selectedInstitution = item;
|
||||||
|
AddOrUpdateRole("PhS_Institutions", item.Id, "Hospital");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task OnPatientSelected(ELookUpItem item)
|
||||||
|
{
|
||||||
|
_selectedPatient = item;
|
||||||
|
AddOrUpdateRole("PhS_Patients", item.Id, "Paciente");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|||||||
|
|
||||||
using Blazored.Modal;
|
using Blazored.Modal;
|
||||||
using Blazored.Toast;
|
using Blazored.Toast;
|
||||||
|
using phronCare.UIBlazor.Services.Sales.Quotes;
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
builder.RootComponents.Add<App>("#app");
|
builder.RootComponents.Add<App>("#app");
|
||||||
@ -52,6 +53,7 @@ await builder.Build().RunAsync();
|
|||||||
static void InjectDependencies(WebAssemblyHostBuilder builder)
|
static void InjectDependencies(WebAssemblyHostBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.AddScoped<ISalesLookupService, SalesLookupService>();
|
builder.Services.AddScoped<ISalesLookupService, SalesLookupService>();
|
||||||
|
builder.Services.AddScoped<IQuoteService,QuoteService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<TicketsService>();
|
builder.Services.AddScoped<TicketsService>();
|
||||||
builder.Services.AddScoped<CustomerService>();
|
builder.Services.AddScoped<CustomerService>();
|
||||||
@ -66,4 +68,5 @@ static void InjectDependencies(WebAssemblyHostBuilder builder)
|
|||||||
builder.Services.AddScoped<PeopleService>();
|
builder.Services.AddScoped<PeopleService>();
|
||||||
builder.Services.AddScoped<ProfessionalSpecialtyService>();
|
builder.Services.AddScoped<ProfessionalSpecialtyService>();
|
||||||
builder.Services.AddScoped<ProductCategoryService>();
|
builder.Services.AddScoped<ProductCategoryService>();
|
||||||
|
|
||||||
}
|
}
|
||||||
10
phronCare.UIBlazor/Services/Sales/Quotes/IQuoteService.cs
Normal file
10
phronCare.UIBlazor/Services/Sales/Quotes/IQuoteService.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
|
||||||
|
namespace phronCare.UIBlazor.Services.Sales.Quotes
|
||||||
|
{
|
||||||
|
public interface IQuoteService
|
||||||
|
{
|
||||||
|
Task<CreateQuoteResult> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||||
|
// Aquí podrías agregar otros métodos: GetById, Search, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
62
phronCare.UIBlazor/Services/Sales/Quotes/QuoteService.cs
Normal file
62
phronCare.UIBlazor/Services/Sales/Quotes/QuoteService.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
|
||||||
|
namespace phronCare.UIBlazor.Services.Sales.Quotes
|
||||||
|
{
|
||||||
|
public class QuoteService : IQuoteService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
|
||||||
|
public QuoteService(HttpClient http)
|
||||||
|
{
|
||||||
|
_http = http;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Envía el EQuoteHeader junto con el formSeriesId al backend
|
||||||
|
/// y recibe el número de presupuesto generado o un error.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<CreateQuoteResult> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
||||||
|
{
|
||||||
|
// 1) Creamos el DTO request tal cual lo espera el controller
|
||||||
|
var request = new CreateFullQuoteRequest
|
||||||
|
{
|
||||||
|
Quote = quote,
|
||||||
|
FormSeriesId = formSeriesId
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2) Enviamos ese DTO al endpoint
|
||||||
|
var response = await _http.PostAsJsonAsync("/api/quote/createfull", request);
|
||||||
|
//var url = $"/api/quote/createfull?formSeriesId={formSeriesId}";
|
||||||
|
//var response = await _http.PostAsJsonAsync(url, quote);
|
||||||
|
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
// Leer mensaje de error del servidor
|
||||||
|
var serverMessage = await response.Content.ReadAsStringAsync();
|
||||||
|
return new CreateQuoteResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
ErrorMessage = serverMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserializar el resultado esperado
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<CreateQuoteResult>();
|
||||||
|
return result!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CreateQuoteResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string QuoteNumber { get; set; } = string.Empty;
|
||||||
|
public string ErrorMessage { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
public class CreateFullQuoteRequest
|
||||||
|
{
|
||||||
|
public EQuoteHeader Quote { get; set; } = default!;
|
||||||
|
public int FormSeriesId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user