using Domain.Constants;
using Domain.Dtos.Stock;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Models.Helpers;
using Models.Interfaces;
using Models.Models;
namespace Models.Repositories.Stock
{
public class PhLSMExpeditionRepository(
PhronCareOperationsHubContext context,
IPhSFormSeriesRepository formSeriesRepository) : IExpeditionRepository
{
private readonly PhronCareOperationsHubContext _context = context;
private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
///
/// Crea la expedición completa (header + details) con numeración de serie y estado emitido.
///
public async Task<(int Id, string Expeditionnumber)> CreateFullExpeditionAsync(
ELSExpeditionHeader expedition, int formSeriesId)
{
using var tx = await _context.Database.BeginTransactionAsync();
try
{
// 1) Numeración (EX-00000000) – mismo patrón que Quotes
var next = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
var series = await _formSeriesRepository.GetByIdAsync(formSeriesId)
?? throw new InvalidOperationException("Serie no encontrada");
var number = $"{series.Letter}-{next:D8}";
var issuedAt = DateTime.Now;
// 2) Completar datos de emisión en el agregado de dominio
expedition.Expeditionnumber = number;
expedition.Issuedate = issuedAt;
expedition.Status = (int)ExpeditionStatus.Emitida;
// 3) Mapear grafo completo Domain -> EF (Header + Details)
// Igual que haces en CreateFullQuoteAsync con EntityMapper.MapEntity(...)
var headerEntity = EntityMapper.MapEntity(expedition);
// 4) Persistir de una (header + colecciones) y confirmar
_context.PhLsmExpeditionHeaders.Add(headerEntity);
await _context.SaveChangesAsync();
await tx.CommitAsync();
return (headerEntity.Id, headerEntity.Expeditionnumber);
}
catch
{
await tx.RollbackAsync();
throw;
}
}
///
/// Devuelve el DTO completo de Expedición (cabecera + ítems) listo para UI/impresión.
///
public async Task GetDtoByIdAsync(int id)
{
// 1) Header + detalles
var header = await _context.PhLsmExpeditionHeaders
.AsNoTracking()
.Include(h => h.PhLsmExpeditionDetails)
.FirstOrDefaultAsync(h => h.Id == id);
if (header is null)
return null;
// 2) Resolver productos (un solo round-trip)
var productIds = header.PhLsmExpeditionDetails
.Select(d => d.ProductId)
.Distinct()
.ToList();
var productMap = productIds.Count == 0
? new Dictionary()
: await _context.PhLsmProducts
.Where(p => productIds.Contains(p.Id))
.Select(p => new
{
p.Id,
p.Name,
p.Descripcion,
p.FactoryCode, // código fábrica (preferido en impresión)
p.ExternalCode, // GTIN
p.RegulatoryCode // PM
})
.ToDictionaryAsync(
p => p.Id,
p => (p.Name, p.Descripcion, p.FactoryCode, p.ExternalCode, p.RegulatoryCode)
);
//// 3) Resolver nombres de ubicaciones (si corresponde)
//var locationIds = header.PhLsmExpeditionDetails
// .Select(d => d.LocationId)
// .Where(l => l.HasValue)
// .Select(l => l!.Value)
// .Distinct()
// .ToList();
//var locationMap = locationIds.Count == 0
// ? new Dictionary()
// : await _context.PhLsmStockLocations
// .Where(l => locationIds.Contains(l.Id))
// .Select(l => new { l.Id, l.Name, l.Address }) // Address opcional
// .ToDictionaryAsync(l => l.Id, l => (l.Name, l.Address));
// 4) Proyección a DTO (ítems)
var items = header.PhLsmExpeditionDetails.Select(d =>
{
productMap.TryGetValue(d.ProductId, out var p);
var productName = !string.IsNullOrWhiteSpace(p.Name) ? p.Name
: (!string.IsNullOrWhiteSpace(p.Descripcion) ? p.Descripcion : string.Empty);
//var locationName = (d.LocationId.HasValue && locationMap.TryGetValue(d.LocationId.Value, out var ln))
// ? ln.Name
// : null;
return new ExpeditionItemDto
{
Id = d.Id,
ProductId = d.ProductId,
FactoryCode = p.FactoryCode ?? string.Empty, // preferido para mostrar
ProductName = productName,
Quantity = d.Quantity,
Batch = d.Batch,
Serial = d.Serial,
Expiration = d.Expiration,
LocationId = d.LocationId,
LocationName = string.Empty, // locationName, // si lo querés mostrar
};
}).ToList();
// 5) Completar cabecera del DTO
var dto = new ExpeditionDto
{
Id = header.Id,
Expeditionnumber = header.Expeditionnumber,
Issuedate = header.Issuedate,
Status = header.Status,
StatusLabel = MapStatus(header.Status),
ExtrainfoJson = header.ExtrainfoJson, // se arma en el momento de imprimir, como definiste
Observations = header.Observations,
// Opcional: si el header tiene BusinessUnitId / SeriesId, podés resolver aquí sus códigos/nombres.
Items = items
};
//// 6) Si todos los detalles comparten la misma ubicación, reflejarla en cabecera (útil para impresión)
//var distinctLocs = items.Select(i => i.LocationId).Where(x => x.HasValue).Distinct().ToList();
//if (distinctLocs.Count == 1)
//{
// dto.LocationId = distinctLocs[0];
// if (dto.LocationId.HasValue && locationMap.TryGetValue(dto.LocationId.Value, out var ln))
// {
// dto.LocationName = ln.Name;
// // dto.LocationAddress = ln.Address; // si tu DTO lo contempla
// }
//}
return dto;
}
// ----- helpers -----
///
/// Mapea el estado entero a etiqueta amigable (enum: Emitida=1, EnTransito=2, EnDestino=3, Retorno=4, Cerrada=5, Anulada=6).
///
private static string MapStatus(int status) => status switch
{
1 => "Emitida",
2 => "En tránsito",
3 => "En destino",
4 => "Retorno",
5 => "Cerrada",
6 => "Anulada",
_ => $"Desconocido ({status})"
};
}
}