feat(deliverynote): add excel export with clinical snapshot parsing #47
@ -22,6 +22,11 @@ public interface IDeliveryNoteDom
|
|||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 50);
|
int pageSize = 50);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exporta a Excel los Delivery Notes filtrados.
|
||||||
|
/// </summary>
|
||||||
|
Task<byte[]> ExportFilteredToExcelAsync(DeliveryNoteSearchParams searchParams);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtiene un Delivery Note por su identificador único.
|
/// Obtiene un Delivery Note por su identificador único.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
using Core.Interfaces;
|
|
||||||
using Domain.Dtos.Sales;
|
using Domain.Dtos.Sales;
|
||||||
using Domain.Entities;
|
using Domain.Entities;
|
||||||
using Domain.Generics;
|
using Domain.Generics;
|
||||||
using Models.Interfaces;
|
using Models.Interfaces;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Transversal.Services;
|
||||||
|
|
||||||
namespace Core.Services
|
namespace Core.Services
|
||||||
{
|
{
|
||||||
@ -46,6 +48,62 @@ namespace Core.Services
|
|||||||
return _deliveryNoteRepository.GetDtoByIdAsync(id);
|
return _deliveryNoteRepository.GetDtoByIdAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task<DeliveryNoteDto?> GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber)
|
public Task<DeliveryNoteDto?> GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(deliveryNoteNumber))
|
if (string.IsNullOrWhiteSpace(deliveryNoteNumber))
|
||||||
@ -125,5 +183,113 @@ namespace Core.Services
|
|||||||
DeliveryNoteNumber = created.Deliverynotenumber
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public partial class PhSDeliveryNoteDetail
|
|||||||
|
|
||||||
public int? QuoteDetailId { get; set; }
|
public int? QuoteDetailId { get; set; }
|
||||||
|
|
||||||
public string Description { get; set; } = null!;
|
public string? Description { get; set; }
|
||||||
|
|
||||||
public decimal Quantity { get; set; }
|
public decimal Quantity { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -1104,9 +1104,7 @@ public partial class PhronCareOperationsHubContext : DbContext
|
|||||||
.HasColumnType("datetime")
|
.HasColumnType("datetime")
|
||||||
.HasColumnName("createdat");
|
.HasColumnName("createdat");
|
||||||
entity.Property(e => e.DeliverynoteId).HasColumnName("deliverynote_id");
|
entity.Property(e => e.DeliverynoteId).HasColumnName("deliverynote_id");
|
||||||
entity.Property(e => e.Description)
|
entity.Property(e => e.Description).HasColumnName("description");
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnName("description");
|
|
||||||
entity.Property(e => e.LineNumber).HasColumnName("line_number");
|
entity.Property(e => e.LineNumber).HasColumnName("line_number");
|
||||||
entity.Property(e => e.Modifiedat)
|
entity.Property(e => e.Modifiedat)
|
||||||
.HasColumnType("datetime")
|
.HasColumnType("datetime")
|
||||||
|
|||||||
@ -176,7 +176,7 @@ namespace Models.Repositories
|
|||||||
OriginType = source.OriginType,
|
OriginType = source.OriginType,
|
||||||
OriginId = source.OriginId,
|
OriginId = source.OriginId,
|
||||||
QuoteDetailId = source.QuoteDetailId,
|
QuoteDetailId = source.QuoteDetailId,
|
||||||
Description = source.Description,
|
Description = source.Description??string.Empty,
|
||||||
Quantity = source.Quantity,
|
Quantity = source.Quantity,
|
||||||
Notes = source.Notes,
|
Notes = source.Notes,
|
||||||
Createdat = source.Createdat,
|
Createdat = source.Createdat,
|
||||||
|
|||||||
@ -150,5 +150,31 @@ namespace phronCare.API.Controllers.Sales
|
|||||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("exportfiltered")]
|
||||||
|
public async Task<IActionResult> ExportFiltered([FromBody] DeliveryNoteSearchParams searchParams)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = await _deliveryNoteService.ExportFilteredToExcelAsync(searchParams);
|
||||||
|
return File(
|
||||||
|
file,
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"Remitos.xlsx");
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||||
|
return BadRequest($"{methodName} Message: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user