using Core.Interfaces; using Domain.Constants; using Domain.Dtos; using Domain.Entities; using Domain.Generics; using Models.Interfaces; using System.Drawing.Printing; using System.Reflection; using Transversal.Services; 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); } public async Task ExportFilteredToExcelAsync(QuoteSearchParams searchParams) { try { // Realiza la búsqueda de clientes con los parámetros proporcionados var searchResult = await _quoteRepository.SearchAsync( searchParams.CustomerId, searchParams.CustomerText, searchParams.QuoteNumber, searchParams.ProfessionalId, searchParams.ProfessionalText, searchParams.InstitutionId, searchParams.InstitutionText, searchParams.PatientId, searchParams.PatientText, searchParams.IssueDateFrom, searchParams.IssueDateTo, searchParams.Status, searchParams.Page, searchParams.PageSize ); // Verifica que se hayan encontrado resultados if (searchResult?.Items is null || !searchResult.Items.Any()) { throw new Exception("No se encontraron clientes para exportar."); } // Llamamos a un método que exporta los datos a Excel var stream = new XLSXExportBase(); // Convertimos los resultados de la búsqueda a un formato adecuado para el exportador var items = searchResult.Items.Select(c => new { c.Quotenumber, Issuedate = c.IssueDate.ToString("dd/MM/yyyy"), // ← string EstimatedDate = c.EstimatedDate?.ToString("dd/MM/yyyy HH:mm"), // ← string c.Status, c.CustomerName, c.ProfessionalName, c.InstitutionName, c.PatientName, c.BusinessUnitName, c.SalespersonName, c.Observations, c.Total }).ToList(); // Genera el archivo Excel var excelFile = stream.ExportExcel(items); // Devuelve el archivo Excel como un array de bytes return excelFile; } catch (Exception ex) { var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; throw new Exception($"{ex.Message}", ex); } } //public async Task GetDtoByQuoteNumberAsync(string quoteNumber) //{ // return await _quoteRepository.GetDtoByIdAsync(quoteNumber); //} #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."); } public async Task GetDtoByQuoteNumberAsync(string quoteNumber) { return await _quoteRepository.GetDtoByQuoteNumberAsync(quoteNumber); } #endregion } }