Compare commits
No commits in common. "0c035f50d6d345b52232c22e27896064242ac63e" and "b2aebafe5506cdbc597f99c39dbb4a6b3c98c89b" have entirely different histories.
0c035f50d6
...
b2aebafe55
4
.gitignore
vendored
4
.gitignore
vendored
@ -397,10 +397,6 @@ FodyWeavers.xsd
|
|||||||
*.msm
|
*.msm
|
||||||
*.msp
|
*.msp
|
||||||
|
|
||||||
# Patch files (temporary)
|
|
||||||
*.patch
|
|
||||||
*.diff
|
|
||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
/Core/obj/Debug/net8.0/Core.csproj.FileListAbsolute.txt
|
/Core/obj/Debug/net8.0/Core.csproj.FileListAbsolute.txt
|
||||||
|
|||||||
@ -23,10 +23,6 @@ namespace Models.Interfaces
|
|||||||
Task<(int Id, string Expeditionnumber)> CreateFullExpeditionAsync(ELSExpeditionHeader expedition, int formSeriesId);
|
Task<(int Id, string Expeditionnumber)> CreateFullExpeditionAsync(ELSExpeditionHeader expedition, int formSeriesId);
|
||||||
Task<ExpeditionDto?> GetDtoByIdAsync(int id);
|
Task<ExpeditionDto?> GetDtoByIdAsync(int id);
|
||||||
Task<PagedResult<ExpeditionDto>> SearchAsync(string? expeditionNumber, string? status, DateTime? issueDateFrom, DateTime? issueDateTo, int? locationId, int page, int pageSize);
|
Task<PagedResult<ExpeditionDto>> SearchAsync(string? expeditionNumber, string? status, DateTime? issueDateFrom, DateTime? issueDateTo, int? locationId, int page, int pageSize);
|
||||||
/// <summary>
|
|
||||||
/// Pasa la expedición a En tránsito y crea las reservas de stock asociadas.
|
|
||||||
/// La operación es transaccional y falla completa si detecta inconsistencias.
|
|
||||||
/// </summary>
|
|
||||||
Task MarkInTransitAsync(int expeditionId);
|
Task MarkInTransitAsync(int expeditionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -406,11 +406,7 @@ namespace Models.Repositories.Stock
|
|||||||
}
|
}
|
||||||
public async Task MarkInTransitAsync(int expeditionId)
|
public async Task MarkInTransitAsync(int expeditionId)
|
||||||
{
|
{
|
||||||
const byte expeditionReservationSourceType = 1;
|
|
||||||
const int reservedStatus = 1;
|
|
||||||
|
|
||||||
var header = await _context.PhLsmExpeditionHeaders
|
var header = await _context.PhLsmExpeditionHeaders
|
||||||
.Include(x => x.PhLsmExpeditionDetails)
|
|
||||||
.FirstOrDefaultAsync(x => x.Id == expeditionId);
|
.FirstOrDefaultAsync(x => x.Id == expeditionId);
|
||||||
|
|
||||||
if (header == null)
|
if (header == null)
|
||||||
@ -419,150 +415,11 @@ namespace Models.Repositories.Stock
|
|||||||
if (header.Status != (int)ExpeditionStatus.Emitida)
|
if (header.Status != (int)ExpeditionStatus.Emitida)
|
||||||
throw new InvalidOperationException("Solo las expediciones en estado 'Emitida' pueden pasar a 'En tránsito'.");
|
throw new InvalidOperationException("Solo las expediciones en estado 'Emitida' pueden pasar a 'En tránsito'.");
|
||||||
|
|
||||||
var details = header.PhLsmExpeditionDetails?.ToList() ?? new List<PhLsmExpeditionDetail>();
|
|
||||||
|
|
||||||
if (details.Count == 0)
|
|
||||||
throw new InvalidOperationException("No se puede pasar la expedición a 'En tránsito' porque no tiene ítems para reservar.");
|
|
||||||
|
|
||||||
var invalidStockItems = details
|
|
||||||
.Where(d => d.StockitemId <= 0)
|
|
||||||
.Select(d => d.Id)
|
|
||||||
.OrderBy(x => x)
|
|
||||||
.ToList();
|
|
||||||
if (invalidStockItems.Count > 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"No se puede pasar la expedición a 'En tránsito' porque existen detalles sin stockitem_id válido. " +
|
|
||||||
$"Detalle(s): {string.Join(", ", invalidStockItems)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var duplicateStockItems = details
|
|
||||||
.GroupBy(d => d.StockitemId)
|
|
||||||
.Where(g => g.Count() > 1)
|
|
||||||
.Select(g => g.Key)
|
|
||||||
.OrderBy(x => x)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (duplicateStockItems.Count > 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"No se puede pasar la expedición a 'En tránsito' porque el mismo StockItem aparece más de una vez en la expedición: " +
|
|
||||||
string.Join(", ", duplicateStockItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
var detailByStockItem = details
|
|
||||||
.Select(d => new
|
|
||||||
{
|
|
||||||
DetailId = d.Id,
|
|
||||||
StockitemId = d.StockitemId,
|
|
||||||
Quantity = d.Quantity
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var stockItemIds = detailByStockItem
|
|
||||||
.Select(x => x.StockitemId)
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var duplicatedReservations = await _context.PhLsmStockReservations
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(r =>
|
|
||||||
r.SourceType == expeditionReservationSourceType &&
|
|
||||||
r.SourceId == expeditionId &&
|
|
||||||
r.Status == reservedStatus &&
|
|
||||||
stockItemIds.Contains(r.StockitemId))
|
|
||||||
.Select(r => r.StockitemId)
|
|
||||||
.Distinct()
|
|
||||||
.OrderBy(x => x)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
if (duplicatedReservations.Count > 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"La expedición ya posee reservas activas para los siguientes StockItem: " +
|
|
||||||
string.Join(", ", duplicatedReservations));
|
|
||||||
}
|
|
||||||
|
|
||||||
var stockItems = await _context.PhLsmStockItems
|
|
||||||
.Where(x => stockItemIds.Contains(x.Id))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var stockItemsById = stockItems.ToDictionary(x => x.Id);
|
|
||||||
|
|
||||||
var missingStockItems = stockItemIds
|
|
||||||
.Where(id => !stockItemsById.ContainsKey(id))
|
|
||||||
.OrderBy(x => x)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (missingStockItems.Count > 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"No se puede pasar la expedición a 'En tránsito' porque algunos StockItem no existen: " +
|
|
||||||
string.Join(", ", missingStockItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
var insufficientAvailability = new List<string>();
|
|
||||||
|
|
||||||
foreach (var item in detailByStockItem.OrderBy(x => x.StockitemId))
|
|
||||||
{
|
|
||||||
var stockItem = stockItemsById[item.StockitemId];
|
|
||||||
var availableQuantity = stockItem.Quantity - stockItem.ReservedQuantity;
|
|
||||||
|
|
||||||
if (item.Quantity > availableQuantity)
|
|
||||||
{
|
|
||||||
insufficientAvailability.Add(
|
|
||||||
$"• StockItem {item.StockitemId} → solicitado: {item.Quantity}, disponible: {availableQuantity}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (insufficientAvailability.Count > 0)
|
|
||||||
{
|
|
||||||
var lines = new List<string>
|
|
||||||
{
|
|
||||||
"No se puede pasar la expedición a 'En tránsito' porque algunos StockItem no tienen cantidad disponible suficiente para reservar."
|
|
||||||
};
|
|
||||||
|
|
||||||
lines.AddRange(insufficientAvailability);
|
|
||||||
|
|
||||||
throw new InvalidOperationException(string.Join(Environment.NewLine, lines));
|
|
||||||
}
|
|
||||||
|
|
||||||
using var tx = await _context.Database.BeginTransactionAsync();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var now = DateTime.Now;
|
|
||||||
|
|
||||||
var reservations = detailByStockItem.Select(item => new PhLsmStockReservation
|
|
||||||
{
|
|
||||||
SourceType = expeditionReservationSourceType,
|
|
||||||
SourceId = expeditionId,
|
|
||||||
StockitemId = item.StockitemId,
|
|
||||||
ReservedQuantity = item.Quantity,
|
|
||||||
Status = reservedStatus,
|
|
||||||
Createdat = now
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
_context.PhLsmStockReservations.AddRange(reservations);
|
|
||||||
|
|
||||||
foreach (var item in detailByStockItem)
|
|
||||||
{
|
|
||||||
var stockItem = stockItemsById[item.StockitemId];
|
|
||||||
stockItem.ReservedQuantity += item.Quantity;
|
|
||||||
stockItem.Modifiedat = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Status = (int)ExpeditionStatus.EnTransito;
|
header.Status = (int)ExpeditionStatus.EnTransito;
|
||||||
header.Modifiedat = now;
|
header.Modifiedat = DateTime.Now;
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
await tx.CommitAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await tx.RollbackAsync();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
namespace Models.Repositories.Stock
|
||||||
|
{
|
||||||
|
internal class PhLSMStockReservationRepository
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user