2026-03-19 17:41:49 -03:00
|
|
|
using Domain.Dtos.Sales;
|
2026-03-23 12:08:57 -03:00
|
|
|
using Domain.Entities;
|
2026-03-20 23:03:46 -03:00
|
|
|
using Domain.Generics;
|
2026-03-19 01:37:43 -03:00
|
|
|
using Models.Interfaces;
|
2026-03-27 16:03:40 -03:00
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using Transversal.Services;
|
2026-03-19 01:37:43 -03:00
|
|
|
|
|
|
|
|
namespace Core.Services
|
|
|
|
|
{
|
|
|
|
|
public class DeliveryNoteService(IPhSDeliveryNoteRepository deliveryNoteRepository) : IDeliveryNoteDom
|
|
|
|
|
{
|
|
|
|
|
private readonly IPhSDeliveryNoteRepository _deliveryNoteRepository = deliveryNoteRepository;
|
|
|
|
|
|
2026-03-20 23:03:46 -03:00
|
|
|
public Task<PagedResult<DeliveryNoteSummaryDto>> 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 17:41:49 -03:00
|
|
|
public Task<DeliveryNoteDto?> GetDtoByIdAsync(int id)
|
2026-03-19 01:37:43 -03:00
|
|
|
{
|
|
|
|
|
if (id <= 0)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(id), "El identificador del remito es inválido.");
|
|
|
|
|
|
2026-03-19 17:41:49 -03:00
|
|
|
return _deliveryNoteRepository.GetDtoByIdAsync(id);
|
2026-03-19 01:37:43 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 16:03:40 -03:00
|
|
|
public async Task<byte[]> 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<DeliveryNoteExcelRow>(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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 17:41:49 -03:00
|
|
|
public Task<DeliveryNoteDto?> GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber)
|
2026-03-19 01:37:43 -03:00
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(deliveryNoteNumber))
|
|
|
|
|
throw new ArgumentException("El número de remito es obligatorio.", nameof(deliveryNoteNumber));
|
|
|
|
|
|
2026-03-19 17:41:49 -03:00
|
|
|
return _deliveryNoteRepository.GetDtoByDeliveryNoteNumberAsync(deliveryNoteNumber.Trim());
|
2026-03-19 01:37:43 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-19 17:41:49 -03:00
|
|
|
public Task<IEnumerable<DeliveryNoteDto>> GetDtosByQuoteIdAsync(int quoteId)
|
2026-03-19 01:37:43 -03:00
|
|
|
{
|
|
|
|
|
if (quoteId <= 0)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(quoteId), "El identificador del presupuesto es inválido.");
|
|
|
|
|
|
2026-03-19 17:41:49 -03:00
|
|
|
return _deliveryNoteRepository.GetDtosByQuoteIdAsync(quoteId);
|
2026-03-19 01:37:43 -03:00
|
|
|
}
|
2026-03-23 12:08:57 -03:00
|
|
|
|
2026-03-24 16:34:38 -03:00
|
|
|
public async Task<DeliveryNoteCreateResponse> CreateAndIssueDeliveryNoteAsync(DeliveryNoteCreateRequest request)
|
2026-03-23 12:08:57 -03:00
|
|
|
{
|
|
|
|
|
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.");
|
|
|
|
|
|
2026-03-24 16:34:38 -03:00
|
|
|
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;
|
|
|
|
|
|
2026-03-23 12:08:57 -03:00
|
|
|
var entity = new EDeliveryNote
|
|
|
|
|
{
|
2026-03-24 16:34:38 -03:00
|
|
|
Deliverynotenumber = deliveryNoteNumber,
|
2026-03-23 12:08:57 -03:00
|
|
|
QuoteId = request.QuoteId,
|
2026-03-23 18:14:00 -03:00
|
|
|
Issuedate = request.IssueDate,
|
2026-03-23 12:08:57 -03:00
|
|
|
CustomerId = request.CustomerId,
|
2026-03-24 16:34:38 -03:00
|
|
|
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()
|
2026-03-23 12:08:57 -03:00
|
|
|
};
|
|
|
|
|
|
2026-03-24 16:34:38 -03:00
|
|
|
var created = await _deliveryNoteRepository.CreateAsync(entity);
|
|
|
|
|
|
|
|
|
|
return new DeliveryNoteCreateResponse
|
2026-03-23 12:08:57 -03:00
|
|
|
{
|
2026-03-24 16:34:38 -03:00
|
|
|
Id = created.Id,
|
|
|
|
|
DeliveryNoteNumber = created.Deliverynotenumber
|
|
|
|
|
};
|
2026-03-23 12:08:57 -03:00
|
|
|
}
|
2026-03-27 16:03:40 -03:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-19 01:37:43 -03:00
|
|
|
}
|
|
|
|
|
}
|