133 lines
6.3 KiB
C#
133 lines
6.3 KiB
C#
using Distribution.Data;
|
|
using Distribution.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Distribution.Features.Stock;
|
|
|
|
public class StockRepository(AppDBContext db) : IStockRepository
|
|
{
|
|
public Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId) =>
|
|
db.StockBalances
|
|
.Include(b => b.Item)
|
|
.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.LocationId == locationId
|
|
&& b.FK_ItemBatchId == null && b.FK_ItemSerialNumberId == null);
|
|
|
|
public Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId) =>
|
|
db.StockBalances
|
|
.Include(b => b.Item)
|
|
.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.LocationId == locationId
|
|
&& b.FK_ItemBatchId == itemBatchId && b.FK_ItemSerialNumberId == itemSerialNumberId);
|
|
|
|
public Task<List<StockBalance>> GetBalancesByLocationAsync(Guid locationId, LocationType locationType) =>
|
|
db.StockBalances
|
|
.Include(b => b.Item)
|
|
.Where(b => b.LocationId == locationId && b.LocationType == locationType)
|
|
.ToListAsync();
|
|
|
|
public Task<List<StockBalance>> GetBalancesAsync(Guid? locationId, LocationType? locationType, Guid? itemId, int page, int pageSize)
|
|
{
|
|
var query = db.StockBalances.Include(b => b.Item).AsQueryable();
|
|
if (locationId.HasValue) query = query.Where(b => b.LocationId == locationId.Value);
|
|
if (locationType.HasValue) query = query.Where(b => b.LocationType == locationType.Value);
|
|
if (itemId.HasValue) query = query.Where(b => b.FK_ItemId == itemId.Value);
|
|
return query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
|
}
|
|
|
|
public Task<int> GetBalancesCountAsync(Guid? locationId, LocationType? locationType, Guid? itemId)
|
|
{
|
|
var query = db.StockBalances.AsQueryable();
|
|
if (locationId.HasValue) query = query.Where(b => b.LocationId == locationId.Value);
|
|
if (locationType.HasValue) query = query.Where(b => b.LocationType == locationType.Value);
|
|
if (itemId.HasValue) query = query.Where(b => b.FK_ItemId == itemId.Value);
|
|
return query.CountAsync();
|
|
}
|
|
|
|
public Task<List<StockBalance>> GetBalancesByItemAsync(Guid itemId) =>
|
|
db.StockBalances
|
|
.Include(b => b.Item)
|
|
.Where(b => b.FK_ItemId == itemId)
|
|
.ToListAsync();
|
|
|
|
public Task<List<StockBalance>> GetLowStockAsync(Guid? locationId)
|
|
{
|
|
var query = db.StockBalances
|
|
.Include(b => b.Item)
|
|
.Where(b => b.LocationType == LocationType.Warehouse
|
|
&& b.Item.ReorderLevel != null
|
|
&& b.Quantity < b.Item.ReorderLevel);
|
|
if (locationId.HasValue) query = query.Where(b => b.LocationId == locationId.Value);
|
|
return query.OrderBy(b => b.Quantity).ToListAsync();
|
|
}
|
|
|
|
public async Task AddBalanceAsync(StockBalance balance) =>
|
|
await db.StockBalances.AddAsync(balance);
|
|
|
|
public void UpdateBalance(StockBalance balance) =>
|
|
db.StockBalances.Update(balance);
|
|
|
|
public async Task AddMovementAsync(StockMovement movement) =>
|
|
await db.StockMovements.AddAsync(movement);
|
|
|
|
public Task<StockMovement?> GetMovementByIdAsync(Guid id) =>
|
|
db.StockMovements.Include(m => m.Item).FirstOrDefaultAsync(m => m.Id == id);
|
|
|
|
public void UpdateMovement(StockMovement movement) =>
|
|
db.StockMovements.Update(movement);
|
|
|
|
public Task<List<StockMovement>> GetMovementsAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize)
|
|
{
|
|
var query = db.StockMovements.Include(m => m.Item).AsQueryable();
|
|
if (locationId.HasValue)
|
|
query = query.Where(m => m.FromLocationId == locationId.Value || m.ToLocationId == locationId.Value);
|
|
if (itemId.HasValue) query = query.Where(m => m.FK_ItemId == itemId.Value);
|
|
if (type.HasValue) query = query.Where(m => m.Type == type.Value);
|
|
if (from.HasValue) query = query.Where(m => m.CreatedAt >= from.Value);
|
|
if (to.HasValue) query = query.Where(m => m.CreatedAt <= to.Value);
|
|
return query.OrderByDescending(m => m.CreatedAt).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
|
}
|
|
|
|
public Task<int> GetMovementsCountAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to)
|
|
{
|
|
var query = db.StockMovements.AsQueryable();
|
|
if (locationId.HasValue)
|
|
query = query.Where(m => m.FromLocationId == locationId.Value || m.ToLocationId == locationId.Value);
|
|
if (itemId.HasValue) query = query.Where(m => m.FK_ItemId == itemId.Value);
|
|
if (type.HasValue) query = query.Where(m => m.Type == type.Value);
|
|
if (from.HasValue) query = query.Where(m => m.CreatedAt >= from.Value);
|
|
if (to.HasValue) query = query.Where(m => m.CreatedAt <= to.Value);
|
|
return query.CountAsync();
|
|
}
|
|
|
|
public Task<ItemBatch?> FindBatchAsync(Guid itemId, string batchNumber) =>
|
|
db.ItemBatches.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.BatchNumber == batchNumber);
|
|
|
|
public async Task AddItemBatchAsync(ItemBatch batch) =>
|
|
await db.ItemBatches.AddAsync(batch);
|
|
|
|
public Task<bool> SerialNumberExistsAsync(Guid itemId, string serialNumber) =>
|
|
db.ItemSerialNumbers.AnyAsync(s => s.FK_ItemId == itemId && s.SerialNumber == serialNumber);
|
|
|
|
public async Task<int> GetNextSerialSequenceAsync(Guid itemId, string prefix)
|
|
{
|
|
// Only serials of the form <prefix><digits> count; anything else (a supplier's own serial that
|
|
// happens to start with the prefix) is ignored for numbering.
|
|
var serials = await db.ItemSerialNumbers
|
|
.AsNoTracking()
|
|
.Where(s => s.FK_ItemId == itemId && s.SerialNumber.StartsWith(prefix))
|
|
.Select(s => s.SerialNumber)
|
|
.ToListAsync();
|
|
|
|
var max = 0L;
|
|
foreach (var serial in serials)
|
|
{
|
|
var suffix = serial[prefix.Length..];
|
|
if (suffix.Length is > 0 and <= 9 && suffix.All(char.IsDigit) && long.TryParse(suffix, out var n))
|
|
max = Math.Max(max, n);
|
|
}
|
|
return (int)max + 1;
|
|
}
|
|
|
|
public async Task AddItemSerialNumberAsync(ItemSerialNumber serial) =>
|
|
await db.ItemSerialNumbers.AddAsync(serial);
|
|
}
|