namespace foodmarket.Application.Inventory;
/// Чистая логика «разбить результат инвентаризации на строки
/// Оприходования (излишки) и Списания (недостача)». Вынесено для юнит-тестов.
///
/// Правила (по модели МойСклада):
/// - diff = факт − расчётное; строки с diff == 0 НЕ участвуют (непосчитанные/совпавшие
/// не трогаются);
/// - diff > 0 → излишек → строка Оприходования, количество = diff;
/// - diff < 0 → недостача → строка Списания, количество = |diff|;
/// - сумма строки = количество × UnitCost (снимок себестоимости).
/// Сама инвентаризация остатки НЕ двигает.
public static class InventoryResult
{
public readonly record struct ResultLine(Guid ProductId, decimal Quantity, decimal UnitCost)
{
public decimal LineTotal => decimal.Round(Quantity * UnitCost, 2);
}
public readonly record struct Split(IReadOnlyList Surplus, IReadOnlyList Shortage)
{
public bool HasSurplus => Surplus.Count > 0;
public bool HasShortage => Shortage.Count > 0;
public decimal SurplusTotal => Surplus.Sum(l => l.LineTotal);
public decimal ShortageTotal => Shortage.Sum(l => l.LineTotal);
}
/// Разбивает строки инвентаризации (productId, diff, unitCost) на излишки/недостачи.
/// diff==0 пропускаются. Количество в Списании положительное (|diff|).
public static Split Build(IEnumerable<(Guid ProductId, decimal Diff, decimal UnitCost)> lines)
{
var surplus = new List();
var shortage = new List();
foreach (var l in lines)
{
if (l.Diff > 0m) surplus.Add(new ResultLine(l.ProductId, l.Diff, l.UnitCost));
else if (l.Diff < 0m) shortage.Add(new ResultLine(l.ProductId, -l.Diff, l.UnitCost));
// diff == 0 — пропускаем
}
return new Split(surplus, shortage);
}
}