2025-04-27 02:19:29 -03:00
|
|
|
|
using Domain.Entities;
|
|
|
|
|
|
using Domain.Generics;
|
2025-05-01 17:59:59 -03:00
|
|
|
|
using Models.Helpers;
|
2025-04-27 02:19:29 -03:00
|
|
|
|
using Models.Interfaces;
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
using Transversal.Services;
|
|
|
|
|
|
|
2025-05-08 15:46:04 -03:00
|
|
|
|
namespace Core.Services
|
2025-04-27 02:19:29 -03:00
|
|
|
|
{
|
|
|
|
|
|
public class QuoteService(
|
|
|
|
|
|
IPhSQuoteHeaderRepository quoteHeaderRepository,
|
2025-05-07 18:35:49 -03:00
|
|
|
|
IPhSQuoteRepository quoteRepository
|
|
|
|
|
|
) : IQuoteDom
|
2025-04-27 02:19:29 -03:00
|
|
|
|
{
|
|
|
|
|
|
#region Declaraciones
|
|
|
|
|
|
private readonly IPhSQuoteHeaderRepository _quoteHeaderRepository = quoteHeaderRepository;
|
2025-05-07 18:35:49 -03:00
|
|
|
|
private readonly IPhSQuoteRepository _quoteRepository = quoteRepository;
|
2025-04-27 02:19:29 -03:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2025-05-01 17:59:59 -03:00
|
|
|
|
#region Presupuestos
|
2025-04-27 02:19:29 -03:00
|
|
|
|
public async Task<PagedResult<EQuoteHeader>> GetAllQuotesAsync(int page = 1, int pageSize = 50)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await _quoteHeaderRepository.GetAllAsync(page, pageSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
public async Task<EQuoteHeader?> GetQuoteByIdAsync(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await _quoteHeaderRepository.GetByIdAsync(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
public async Task<IEnumerable<EQuoteHeader>> GetQuotesByCustomerAsync(int customerId)
|
|
|
|
|
|
{
|
2025-05-01 17:59:59 -03:00
|
|
|
|
return await _quoteHeaderRepository.GetByCustomerIdAsync(customerId);
|
2025-04-27 02:19:29 -03:00
|
|
|
|
}
|
2025-05-01 17:59:59 -03:00
|
|
|
|
public async 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)
|
2025-04-27 02:19:29 -03:00
|
|
|
|
{
|
2025-05-01 17:59:59 -03:00
|
|
|
|
return await _quoteHeaderRepository.SearchAsync(
|
|
|
|
|
|
customerId,
|
2025-04-27 02:19:29 -03:00
|
|
|
|
quoteNumber,
|
|
|
|
|
|
professionalId,
|
|
|
|
|
|
institutionId,
|
|
|
|
|
|
patientId,
|
|
|
|
|
|
issueDateFrom,
|
|
|
|
|
|
issueDateTo,
|
|
|
|
|
|
status,
|
|
|
|
|
|
page,
|
|
|
|
|
|
pageSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
public async Task UpdateQuoteAsync(EQuoteHeader quote)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _quoteHeaderRepository.UpdateAsync(quote);
|
|
|
|
|
|
}
|
|
|
|
|
|
public async Task DeleteQuoteAsync(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _quoteHeaderRepository.DeleteAsync(id);
|
|
|
|
|
|
}
|
2025-05-01 17:59:59 -03:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Exportación
|
2025-04-27 02:19:29 -03:00
|
|
|
|
public async Task<byte[]> ExportFilteredQuotesToExcelAsync(QuoteSearchParams searchParams)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var searchResult = await SearchQuotesAsync(
|
|
|
|
|
|
searchParams.CustomerId,
|
|
|
|
|
|
searchParams.QuoteNumber,
|
|
|
|
|
|
searchParams.ProfessionalId,
|
|
|
|
|
|
searchParams.InstitutionId,
|
|
|
|
|
|
searchParams.PatientId,
|
|
|
|
|
|
searchParams.IssueDateFrom,
|
|
|
|
|
|
searchParams.IssueDateTo,
|
|
|
|
|
|
searchParams.Status,
|
|
|
|
|
|
searchParams.Page,
|
2025-05-01 17:59:59 -03:00
|
|
|
|
searchParams.PageSize);
|
2025-04-27 02:19:29 -03:00
|
|
|
|
|
|
|
|
|
|
if (searchResult?.Items == null || !searchResult.Items.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("No se encontraron presupuestos para exportar.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var stream = new XLSXExportBase();
|
|
|
|
|
|
|
|
|
|
|
|
var quotesData = searchResult.Items.Select(q => new
|
|
|
|
|
|
{
|
|
|
|
|
|
NúmeroPresupuesto = q.Quotenumber,
|
|
|
|
|
|
Estado = q.Status,
|
|
|
|
|
|
FechaEmisión = q.Issuedate.ToString("yyyy-MM-dd"),
|
|
|
|
|
|
FechaTentativa = q.Estimateddate?.ToString("yyyy-MM-dd"),
|
|
|
|
|
|
ImporteAprobado = q.Approvedamount,
|
2025-05-01 17:59:59 -03:00
|
|
|
|
Profesional = q.PhSQuoteRoles.FirstOrDefault(r => r.Entitytype == PhSEntityTypes.Professional)?.Entitytype,
|
|
|
|
|
|
Institución = q.PhSQuoteRoles.FirstOrDefault(r => r.Entitytype == PhSEntityTypes.Institution)?.Entitytype,
|
|
|
|
|
|
Paciente = q.PhSQuoteRoles.FirstOrDefault(r => r.Entitytype == PhSEntityTypes.Patient)?.Entitytype
|
2025-04-27 02:19:29 -03:00
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
2025-05-01 17:59:59 -03:00
|
|
|
|
return stream.ExportExcel(quotesData);
|
2025-04-27 02:19:29 -03:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
|
|
|
|
|
throw new Exception($"{methodName} Message: {ex.Message}", ex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
2025-05-05 22:50:02 -03:00
|
|
|
|
|
2025-05-06 21:00:03 -03:00
|
|
|
|
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
|
|
|
|
|
public async Task<string> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
|
|
|
|
|
{
|
2025-05-07 18:35:49 -03:00
|
|
|
|
// 1. Validaciones antes de iniciar transacción
|
|
|
|
|
|
ValidateQuote(quote);
|
|
|
|
|
|
return await _quoteRepository.CreateFullQuoteAsync(quote, formSeriesId);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
2025-05-06 21:00:03 -03:00
|
|
|
|
|
2025-05-07 18:35:49 -03:00
|
|
|
|
#region Validaciones QuoteCreate
|
|
|
|
|
|
private void ValidateQuote(EQuoteHeader quote)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (quote == null)
|
|
|
|
|
|
throw new ArgumentNullException(nameof(quote), "El presupuesto no puede ser nulo.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
|
2025-05-07 18:35:49 -03:00
|
|
|
|
if (quote.CustomerId <= 0)
|
|
|
|
|
|
throw new ArgumentException("Debe seleccionar un cliente.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
|
2025-05-07 18:35:49 -03:00
|
|
|
|
if (quote.PeopleId <= 0)
|
|
|
|
|
|
throw new ArgumentException("Debe seleccionar un vendedor.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
|
2025-05-07 18:35:49 -03:00
|
|
|
|
if (string.IsNullOrWhiteSpace(quote.Currency))
|
|
|
|
|
|
throw new ArgumentException("La moneda es obligatoria.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
|
2025-05-07 18:35:49 -03:00
|
|
|
|
if (quote.PhSQuoteDetails == null || !quote.PhSQuoteDetails.Any())
|
|
|
|
|
|
throw new InvalidOperationException("Debe incluir al menos un producto.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
|
2025-05-07 18:35:49 -03:00
|
|
|
|
foreach (var detail in quote.PhSQuoteDetails)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (detail.Quantity <= 0)
|
|
|
|
|
|
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.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
}
|
2025-05-07 18:35:49 -03:00
|
|
|
|
|
|
|
|
|
|
if (quote.PhSQuoteRoles == null || !quote.PhSQuoteRoles.Any())
|
|
|
|
|
|
throw new InvalidOperationException("Debe asignar al menos un rol (profesional, paciente o institución).");
|
|
|
|
|
|
|
|
|
|
|
|
var hasProfessional = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Professional);
|
|
|
|
|
|
var hasPatient = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Patient);
|
|
|
|
|
|
var hasInstitution = quote.PhSQuoteRoles.Any(r => r.Entitytype == PhSEntityTypes.Institution);
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasProfessional)
|
|
|
|
|
|
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)
|
2025-05-06 21:00:03 -03:00
|
|
|
|
{
|
2025-05-07 18:35:49 -03:00
|
|
|
|
foreach (var tax in quote.PhSQuoteTaxes)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (tax.Taxrate < 0 || tax.Taxrate > 100)
|
|
|
|
|
|
throw new ArgumentException($"La alícuota del impuesto '{tax.Taxname}' no es válida.");
|
|
|
|
|
|
}
|
2025-05-06 21:00:03 -03:00
|
|
|
|
}
|
2025-05-07 18:35:49 -03:00
|
|
|
|
|
|
|
|
|
|
if (quote.Total < 0)
|
|
|
|
|
|
throw new ArgumentException("El total del presupuesto no puede ser negativo.");
|
2025-05-06 21:00:03 -03:00
|
|
|
|
}
|
2025-05-07 18:35:49 -03:00
|
|
|
|
|
2025-05-06 21:00:03 -03:00
|
|
|
|
#endregion
|
2025-04-27 02:19:29 -03:00
|
|
|
|
}
|
2025-05-06 21:00:03 -03:00
|
|
|
|
}
|