using Domain.Dtos.Sales; using Domain.Entities; using Domain.Generics; using Models.Interfaces; using System.Reflection; using System.Text.Json; using Transversal.Services; namespace Core.Services { public class DeliveryNoteService(IPhSDeliveryNoteRepository deliveryNoteRepository) : IDeliveryNoteDom { private readonly IPhSDeliveryNoteRepository _deliveryNoteRepository = deliveryNoteRepository; public Task> SearchAsync( int? customerId, string? customerText, string? deliveryNoteNumber, int? quoteId, string? quoteNumber, DateTime? issueDateFrom, DateTime? issueDateTo, string? status, int page = 1, int pageSize = 50) { page = page <= 0 ? 1 : page; pageSize = pageSize <= 0 ? 50 : pageSize; return _deliveryNoteRepository.SearchAsync( customerId, string.IsNullOrWhiteSpace(customerText) ? null : customerText.Trim(), string.IsNullOrWhiteSpace(deliveryNoteNumber) ? null : deliveryNoteNumber.Trim(), quoteId, string.IsNullOrWhiteSpace(quoteNumber) ? null : quoteNumber.Trim(), issueDateFrom, issueDateTo, string.IsNullOrWhiteSpace(status) ? null : status.Trim(), page, pageSize); } public Task GetDtoByIdAsync(int id) { if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id), "El identificador del remito es inválido."); return _deliveryNoteRepository.GetDtoByIdAsync(id); } public async Task ExportFilteredToExcelAsync(DeliveryNoteSearchParams searchParams) { ArgumentNullException.ThrowIfNull(searchParams); try { var searchResult = await _deliveryNoteRepository.SearchAsync( searchParams.CustomerId, string.IsNullOrWhiteSpace(searchParams.CustomerText) ? null : searchParams.CustomerText.Trim(), string.IsNullOrWhiteSpace(searchParams.DeliveryNoteNumber) ? null : searchParams.DeliveryNoteNumber.Trim(), searchParams.QuoteId, string.IsNullOrWhiteSpace(searchParams.QuoteNumber) ? null : searchParams.QuoteNumber.Trim(), searchParams.IssueDateFrom, searchParams.IssueDateTo, string.IsNullOrWhiteSpace(searchParams.Status) ? null : searchParams.Status.Trim(), searchParams.Page <= 0 ? 1 : searchParams.Page, searchParams.PageSize <= 0 ? 50 : searchParams.PageSize); if (searchResult?.Items is null || !searchResult.Items.Any()) throw new Exception("No se encontraron remitos para exportar."); var items = searchResult.Items.ToList(); var exportRows = new List(items.Count); foreach (var deliveryNote in items) { var dto = await _deliveryNoteRepository.GetDtoByIdAsync(deliveryNote.Id); var snapshot = DeliveryNoteSnapshotInfo.FromJson(dto?.ExtraInfoJson); exportRows.Add(new DeliveryNoteExcelRow { DeliveryNoteNumber = deliveryNote.DeliveryNoteNumber, IssueDate = deliveryNote.IssueDate.ToString("dd/MM/yyyy"), QuoteNumber = deliveryNote.QuoteNumber, CustomerName = deliveryNote.CustomerName, Status = deliveryNote.Status, ProfessionalName = snapshot.ProfessionalName, InstitutionName = snapshot.InstitutionName, PatientName = snapshot.PatientName, SurgeryDate = snapshot.SurgeryDate, Observations = deliveryNote.Observations, PrintCount = deliveryNote.PrintCount, CreatedAt = deliveryNote.CreatedAt.ToString("dd/MM/yyyy HH:mm") }); } var stream = new XLSXExportBase(); return stream.ExportExcel(exportRows); } catch (Exception ex) { var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; throw new Exception($"{methodName} Message: {ex.Message}", ex); } } public Task GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber) { if (string.IsNullOrWhiteSpace(deliveryNoteNumber)) throw new ArgumentException("El número de remito es obligatorio.", nameof(deliveryNoteNumber)); return _deliveryNoteRepository.GetDtoByDeliveryNoteNumberAsync(deliveryNoteNumber.Trim()); } public Task> GetDtosByQuoteIdAsync(int quoteId) { if (quoteId <= 0) throw new ArgumentOutOfRangeException(nameof(quoteId), "El identificador del presupuesto es inválido."); return _deliveryNoteRepository.GetDtosByQuoteIdAsync(quoteId); } public async Task CreateAndIssueDeliveryNoteAsync(DeliveryNoteCreateRequest request) { ArgumentNullException.ThrowIfNull(request); if (string.IsNullOrWhiteSpace(request.DeliveryNoteNumber)) throw new ArgumentException("El número de remito es obligatorio.", nameof(request.DeliveryNoteNumber)); if (request.CustomerId <= 0) throw new ArgumentException("Debe seleccionar un cliente.", nameof(request.CustomerId)); if (request.IssueDate == default) throw new ArgumentException("La fecha de emisión es obligatoria.", nameof(request.IssueDate)); if (request.Items is null || request.Items.Count == 0) throw new InvalidOperationException("Debe incluir al menos un ítem."); if (request.Items.Any(i => i.Quantity <= 0)) throw new InvalidOperationException("Todas las cantidades deben ser mayores a cero."); if (request.Items.Any(i => string.IsNullOrWhiteSpace(i.Description))) throw new InvalidOperationException("Todos los ítems deben incluir descripción."); var deliveryNoteNumber = request.DeliveryNoteNumber.Trim(); if (await _deliveryNoteRepository.ExistsByDeliveryNoteNumberAsync(deliveryNoteNumber)) throw new InvalidOperationException($"Ya existe un remito con el número '{deliveryNoteNumber}'."); var now = DateTime.Now; var entity = new EDeliveryNote { Deliverynotenumber = deliveryNoteNumber, QuoteId = request.QuoteId, Issuedate = request.IssueDate, CustomerId = request.CustomerId, Status = "Emitido", Observations = string.IsNullOrWhiteSpace(request.Observations) ? null : request.Observations.Trim(), ExtrainfoJson = string.IsNullOrWhiteSpace(request.ExtraInfoJson) ? null : request.ExtraInfoJson.Trim(), Printcount = 0, Createdat = now, PhSDeliveryNoteDetails = request.Items .Select((item, index) => new EDeliveryNoteDetail { LineNumber = index + 1, OriginType = item.OriginType, OriginId = item.OriginId, QuoteDetailId = item.QuoteDetailId, Description = item.Description.Trim(), Quantity = item.Quantity, Notes = string.IsNullOrWhiteSpace(item.Notes) ? string.Empty : item.Notes.Trim(), Createdat = now }) .ToList() }; var created = await _deliveryNoteRepository.CreateAsync(entity); return new DeliveryNoteCreateResponse { Id = created.Id, DeliveryNoteNumber = created.Deliverynotenumber }; } private sealed class DeliveryNoteExcelRow { public string DeliveryNoteNumber { get; set; } = string.Empty; public string IssueDate { get; set; } = string.Empty; public string? QuoteNumber { get; set; } public string? CustomerName { get; set; } public string? Status { get; set; } public string? ProfessionalName { get; set; } public string? InstitutionName { get; set; } public string? PatientName { get; set; } public string? SurgeryDate { get; set; } public string? Observations { get; set; } public int PrintCount { get; set; } public string CreatedAt { get; set; } = string.Empty; } private sealed class DeliveryNoteSnapshotInfo { public string? ProfessionalName { get; private set; } public string? InstitutionName { get; private set; } public string? PatientName { get; private set; } public string? SurgeryDate { get; private set; } public static DeliveryNoteSnapshotInfo FromJson(string? extraInfoJson) { var snapshot = new DeliveryNoteSnapshotInfo(); if (string.IsNullOrWhiteSpace(extraInfoJson)) return snapshot; try { using var document = JsonDocument.Parse(extraInfoJson); var root = document.RootElement; snapshot.ProfessionalName = ReadString(root, "professional", "professionalName", "doctor", "doctorName", "medico", "medicoNombre"); snapshot.InstitutionName = ReadString(root, "institution", "institutionName", "hospital", "hospitalName", "institucion", "institucionNombre"); snapshot.PatientName = ReadString(root, "patient", "patientName", "paciente", "pacienteNombre"); snapshot.SurgeryDate = ReadDate(root, "surgeryDate", "estimatedDate", "fechaCirugia", "surgery_date", "estimated_date"); } catch { return snapshot; } return snapshot; } private static string? ReadString(JsonElement root, params string[] propertyNames) { foreach (var propertyName in propertyNames) { if (!TryGetPropertyIgnoreCase(root, propertyName, out var value)) continue; if (value.ValueKind == JsonValueKind.String) return value.GetString(); if (value.ValueKind != JsonValueKind.Null && value.ValueKind != JsonValueKind.Undefined) return value.ToString(); } return null; } private static string? ReadDate(JsonElement root, params string[] propertyNames) { foreach (var propertyName in propertyNames) { if (!TryGetPropertyIgnoreCase(root, propertyName, out var value)) continue; if (value.ValueKind == JsonValueKind.String) { var raw = value.GetString(); if (string.IsNullOrWhiteSpace(raw)) return null; if (DateTime.TryParse(raw, out var parsedDate)) return parsedDate.ToString("dd/MM/yyyy"); return raw; } if (value.ValueKind != JsonValueKind.Null && value.ValueKind != JsonValueKind.Undefined) return value.ToString(); } return null; } private static bool TryGetPropertyIgnoreCase(JsonElement element, string propertyName, out JsonElement value) { foreach (var property in element.EnumerateObject()) { if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase)) { value = property.Value; return true; } } value = default; return false; } } } }