using Domain.Dtos; using Domain.Constants; using Domain.Entities; using Domain.Generics; using Models.Interfaces; namespace Core.Services { public class QuoteService(IQuoteRepository quoteRepository):IQuoteDom { #region Declaraciones private readonly IQuoteRepository _quoteRepository = quoteRepository; //private readonly IPhSQuoteRepository _quoteRepository = quoteRepository; #endregion #region Presupuestos public async Task> 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) { return await _quoteRepository.SearchAsync( customerId, customerText, quoteNumber, professionalId, professionalText, institutionId, institutionText, patientId, patientText, issueDateFrom, issueDateTo, status, page, pageSize); } public async Task GetDtoByIdAsync(int id) { return await _quoteRepository.GetDtoByIdAsync(id); } #endregion #region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos) public async Task<(int Id, string Quotenumber)> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId) { // 1. Validaciones antes de iniciar transacción ValidateQuote(quote); return await _quoteRepository.CreateFullQuoteAsync(quote, formSeriesId); } #endregion public async Task AuthorizeQuoteAsync(int quoteId, List items) { if (items == null) throw new InvalidOperationException("No se recibieron ítems para autorizar."); // 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 return await _quoteRepository.AuthorizeQuoteAsync(quoteId, approvedDetails); } #region Validaciones QuoteCreate private void ValidateQuote(EQuoteHeader quote) { if (quote == null) throw new ArgumentNullException(nameof(quote), "El presupuesto no puede ser nulo."); if (quote.CustomerId <= 0) 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."); 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."); } 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 == EntityTypes.Professional); var hasPatient = quote.PhSQuoteRoles.Any(r => r.Entitytype == EntityTypes.Patient); var hasInstitution = quote.PhSQuoteRoles.Any(r => r.Entitytype == EntityTypes.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) { 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."); } } if (quote.Total < 0) throw new ArgumentException("El total del presupuesto no puede ser negativo."); } #endregion } }