food-market/tests/food-market.UnitTests/InventoryResultTests.cs
nns 0d44cdabaf feat(inventory): модель МойСклада — инвентаризация не двигает остатки
Замечание владельца: инвентаризация стояла «Проведено» без оприходования/списания.
Переделано по МойСкладу (docs/inventory-moysklad-reference.md):

- InventoryStatus Draft→Completed (было Posted; значение 1 то же). Инвентаризация
  остатки НЕ двигает: endpoint complete замораживает строки без движений.
- create-enter (излишки diff>0) / create-loss (недостача diff<0) — создают черновики
  Оприходования/Списания со строками разницы + ссылка InventoryDocId. Повторно нельзя (409).
- Кнопки «Применить результаты» НЕТ (поправка Mac-Claude — не одобрено владельцем).
- unpost («вернуть в черновик») отменяет проведение и удаляет связанные документы.
- post/unpost — временная совместимость для мобильного «FM Склад» (post = завершить +
  создать и провести оба документа); помечено в docs/mobile-api.md.
- Enter/Loss: поле InventoryDocId (+ миграция Phase38) + в DTO «на основании инвентаризации №…».
- InventoryResult (application) — чистая разбивка diff→излишки/недостача (unit-тесты:
  разница/сумма/только посчитанные строки/округление).
- InventoryMigrationController (dry-run/apply): старые Posted+InventoryAdjustment →
  связанные проведённые документы переметкой движений (не задваивает), с проверкой
  неизменности остатков. Скрипт deploy/migrate-inventory-to-docs.sh (backup + запуск).
- Демо-сидеры: инвентаризации теперь Completed без прямых движений.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:34:05 +05:00

77 lines
2.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using foodmarket.Application.Inventory;
using Xunit;
namespace foodmarket.UnitTests;
/// <summary>Логика разбивки результата инвентаризации на Оприходование/Списание
/// (модель МойСклада). diff==0 не участвует; суммы = кол-во × себестоимость.</summary>
public class InventoryResultTests
{
private static (Guid, decimal, decimal) L(decimal diff, decimal cost) => (Guid.NewGuid(), diff, cost);
[Fact]
public void Surplus_goes_to_enter_shortage_to_loss()
{
var p1 = Guid.NewGuid(); var p2 = Guid.NewGuid();
var split = InventoryResult.Build(new[]
{
(p1, 3m, 100m), // излишек +3
(p2, -2m, 50m), // недостача -2
});
Assert.Single(split.Surplus);
Assert.Single(split.Shortage);
Assert.Equal(p1, split.Surplus[0].ProductId);
Assert.Equal(3m, split.Surplus[0].Quantity);
Assert.Equal(p2, split.Shortage[0].ProductId);
Assert.Equal(2m, split.Shortage[0].Quantity); // |-2|
}
[Fact]
public void Zero_diff_lines_are_ignored()
{
var split = InventoryResult.Build(new[]
{
L(0m, 100m), // совпало — не трогаем
L(0m, 999m),
L(1m, 10m), // только эта
});
Assert.Single(split.Surplus);
Assert.Empty(split.Shortage);
Assert.Equal(1m, split.Surplus[0].Quantity);
}
[Fact]
public void Sums_are_quantity_times_cost()
{
var split = InventoryResult.Build(new[]
{
L(2m, 150m), // +2 × 150 = 300
L(5m, 20m), // +5 × 20 = 100
L(-3m, 40m), // 3 × 40 = 120 (недостача)
});
Assert.Equal(400m, split.SurplusTotal);
Assert.Equal(120m, split.ShortageTotal);
Assert.True(split.HasSurplus);
Assert.True(split.HasShortage);
}
[Fact]
public void Line_total_rounds_to_two_decimals()
{
var split = InventoryResult.Build(new[] { L(3m, 548.6441m) });
// 3 × 548.6441 = 1645.9323 → округление до 2 знаков
Assert.Equal(1645.93m, split.Surplus[0].LineTotal);
}
[Fact]
public void No_diffs_gives_empty_split()
{
var split = InventoryResult.Build(new[] { L(0m, 10m), L(0m, 20m) });
Assert.False(split.HasSurplus);
Assert.False(split.HasShortage);
Assert.Equal(0m, split.SurplusTotal);
Assert.Equal(0m, split.ShortageTotal);
}
}