2025-05-15 19:24:12 -03:00
|
|
|
|
using Domain.Dtos;
|
2025-05-13 12:08:38 -03:00
|
|
|
|
using Domain.Constants;
|
|
|
|
|
|
using Domain.Entities;
|
2025-04-27 02:19:29 -03:00
|
|
|
|
using Domain.Generics;
|
|
|
|
|
|
using Models.Interfaces;
|
2025-09-04 18:15:15 -03:00
|
|
|
|
using Core.Interfaces;
|
2025-04-27 02:19:29 -03:00
|
|
|
|
|
2025-05-08 15:46:04 -03:00
|
|
|
|
namespace Core.Services
|
2025-04-27 02:19:29 -03:00
|
|
|
|
{
|
2025-05-13 12:08:38 -03:00
|
|
|
|
public class QuoteService(IQuoteRepository quoteRepository):IQuoteDom
|
2025-04-27 02:19:29 -03:00
|
|
|
|
{
|
|
|
|
|
|
#region Declaraciones
|
2025-05-13 12:08:38 -03:00
|
|
|
|
private readonly IQuoteRepository _quoteRepository = quoteRepository;
|
|
|
|
|
|
//private readonly IPhSQuoteRepository _quoteRepository = quoteRepository;
|
2025-04-27 02:19:29 -03:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2025-05-16 17:19:40 -03:00
|
|
|
|
#region Presupuestos
|
2025-05-13 12:08:38 -03:00
|
|
|
|
public async Task<PagedResult<QuoteDto>> SearchAsync(int? customerId, string? customerText, string? quoteNumber, int? professionalId, string? professionalText, int? institutionId, string? institutionText, int? patientId, string? patientText, DateTime? issueDateFrom, DateTime? issueDateTo, string? status, int page, int pageSize)
|
2025-04-27 02:19:29 -03:00
|
|
|
|
{
|
2025-05-13 12:08:38 -03:00
|
|
|
|
return await _quoteRepository.SearchAsync(
|
2025-05-01 17:59:59 -03:00
|
|
|
|
customerId,
|
2025-05-13 12:08:38 -03:00
|
|
|
|
customerText,
|
2025-04-27 02:19:29 -03:00
|
|
|
|
quoteNumber,
|
|
|
|
|
|
professionalId,
|
2025-05-13 12:08:38 -03:00
|
|
|
|
professionalText,
|
2025-04-27 02:19:29 -03:00
|
|
|
|
institutionId,
|
2025-05-13 12:08:38 -03:00
|
|
|
|
institutionText,
|
2025-04-27 02:19:29 -03:00
|
|
|
|
patientId,
|
2025-05-13 12:08:38 -03:00
|
|
|
|
patientText,
|
2025-04-27 02:19:29 -03:00
|
|
|
|
issueDateFrom,
|
|
|
|
|
|
issueDateTo,
|
|
|
|
|
|
status,
|
|
|
|
|
|
page,
|
|
|
|
|
|
pageSize);
|
|
|
|
|
|
}
|
2025-05-16 17:19:40 -03:00
|
|
|
|
|
|
|
|
|
|
public async Task<QuoteDto?> GetDtoByIdAsync(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await _quoteRepository.GetDtoByIdAsync(id);
|
|
|
|
|
|
}
|
2025-08-18 00:47:37 -03:00
|
|
|
|
|
|
|
|
|
|
//public async Task<QuoteDto?> GetDtoByQuoteNumberAsync(string quoteNumber)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// return await _quoteRepository.GetDtoByIdAsync(quoteNumber);
|
|
|
|
|
|
//}
|
2025-04-27 02:19:29 -03:00
|
|
|
|
#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)
|
2025-05-18 03:21:48 -03:00
|
|
|
|
public async Task<(int Id, string Quotenumber)> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
|
2025-05-06 21:00:03 -03:00
|
|
|
|
{
|
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-29 19:21:57 -03:00
|
|
|
|
public async Task<bool> AuthorizeQuoteAsync(int quoteId, List<QuoteAuthorizationDto> items)
|
|
|
|
|
|
{
|
2025-05-29 21:16:55 -03:00
|
|
|
|
if (items == null)
|
|
|
|
|
|
throw new InvalidOperationException("No se recibieron ítems para autorizar.");
|
2025-05-29 19:21:57 -03:00
|
|
|
|
|
2025-05-29 21:16:55 -03:00
|
|
|
|
// Si no hay ítems aprobados, consideramos que es una anulación
|
|
|
|
|
|
var approvedDetails = items
|
|
|
|
|
|
.Where(i => i.Approved)
|
|
|
|
|
|
.Select(i =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!i.ApprovedQuantity.HasValue || !i.ApprovedUnitPrice.HasValue)
|
|
|
|
|
|
throw new InvalidOperationException("Los ítems aprobados deben tener cantidad y precio válidos.");
|
|
|
|
|
|
|
|
|
|
|
|
return new EQuoteDetail
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = i.Id,
|
|
|
|
|
|
Approved = true,
|
|
|
|
|
|
Approvedquantity = i.ApprovedQuantity,
|
|
|
|
|
|
Approvedunitprice = i.ApprovedUnitPrice
|
|
|
|
|
|
};
|
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// Este llamado puede interpretar lista vacía como anulación completa
|
2025-05-29 19:21:57 -03:00
|
|
|
|
return await _quoteRepository.AuthorizeQuoteAsync(quoteId, approvedDetails);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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).");
|
|
|
|
|
|
|
2025-05-13 12:08:38 -03:00
|
|
|
|
var hasProfessional = quote.PhSQuoteRoles.Any(r => r.Entitytype == EntityTypes.Professional);
|
|
|
|
|
|
var hasPatient = quote.PhSQuoteRoles.Any(r => r.Entitytype == EntityTypes.Patient);
|
|
|
|
|
|
var hasInstitution = quote.PhSQuoteRoles.Any(r => r.Entitytype == EntityTypes.Institution);
|
2025-05-07 18:35:49 -03:00
|
|
|
|
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-08-18 00:47:37 -03:00
|
|
|
|
public async Task<QuoteDto?> GetDtoByQuoteNumberAsync(string quoteNumber)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await _quoteRepository.GetDtoByQuoteNumberAsync(quoteNumber);
|
|
|
|
|
|
}
|
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
|
|
|
|
}
|