using FluentAssertions; using foodmarket.Application.Money; using foodmarket.Domain.Organizations; using Xunit; namespace foodmarket.UnitTests; /// Чистые денежные формулы модуля «Деньги» (): /// статус оплаты приёмки, ожидаемая наличность смены, инвариант остатка счёта, /// долг поставщика, защита от переплаты. Эти же формулы использует сервис, /// контроллеры и отчёты — тест фиксирует их поведение без БД. public class MoneyMathTests { // ── Статус оплаты приёмки ──────────────────────────────────────────── [Theory] [InlineData(1000, 0, "unpaid")] [InlineData(1000, 400, "partial")] [InlineData(1000, 999, "partial")] [InlineData(1000, 999.9999, "paid")] // в пределах ε считаем оплаченным [InlineData(1000, 1000, "paid")] [InlineData(1000, 1200, "paid")] // переплату отображаем как «оплачено» [InlineData(0, 0, "paid")] // платить нечего [InlineData(-5, 0, "paid")] public void PaymentStatus_classifies(decimal total, decimal payed, string expected) => MoneyMath.PaymentStatus(total, payed).Should().Be(expected); // ── Ожидаемая наличность смены ─────────────────────────────────────── [Fact] public void ExpectedCash_is_float_plus_cash_sales_minus_returns_plus_in_minus_out() { // разменник 20 000 + продажи нал 50 000 − возвраты нал 3 000 // + внесения 1 000 − выплаты (инкассация) 40 000 = 28 000 MoneyMath.ExpectedCash(20_000m, 50_000m, 3_000m, 1_000m, 40_000m) .Should().Be(28_000m); } [Fact] public void ExpectedCash_empty_shift_equals_opening_float() => MoneyMath.ExpectedCash(15_000m, 0, 0, 0, 0).Should().Be(15_000m); [Theory] [InlineData(10_000, 10_000, 0)] // ровно сошлось [InlineData(9_500, 10_000, -500)] // недостача [InlineData(10_300, 10_000, 300)] // излишек public void CashDifference_is_actual_minus_expected(decimal actual, decimal expected, decimal diff) => MoneyMath.CashDifference(actual, expected).Should().Be(diff); // ── Инвариант остатка счёта ────────────────────────────────────────── [Fact] public void AccountBalance_is_opening_plus_in_minus_out() => MoneyMath.AccountBalance(100_000m, 75_000m, 40_000m).Should().Be(135_000m); [Fact] public void AccountBalance_can_go_negative() => MoneyMath.AccountBalance(0m, 0m, 5_000m).Should().Be(-5_000m); // ── Долг поставщика ────────────────────────────────────────────────── [Fact] public void SupplierDebt_supplies_minus_paid() { // 3 приёмки на 300 000, оплачено 100 000, возвратов/корректировок нет. MoneyMath.SupplierDebt(300_000m, 100_000m, 0, 0, 0, 0) .Should().Be(200_000m); } [Fact] public void SupplierDebt_returns_reduce_debt() { // приёмки 300 000 (оплачено 300 000), возврат на 50 000 (деньги назад не получили) // → поставщик должен нам 50 000 → долг −50 000. MoneyMath.SupplierDebt(300_000m, 300_000m, 50_000m, 0m, 0, 0) .Should().Be(-50_000m); } [Fact] public void SupplierDebt_starting_adjustment_adds_debt() { // стартовый долг «мы должны» 500 000 при переходе на систему. MoneyMath.SupplierDebt(0, 0, 0, 0, 500_000m, 0) .Should().Be(500_000m); } [Fact] public void SupplierDebt_full_formula() { // приёмки 1 000 000 (оплачено 600 000) − возвраты 100 000 (вернули деньги 100 000) // + корр.(мы должны 50 000 − они должны 20 000) // = 400 000 − 0 + 30 000 = 430 000 MoneyMath.SupplierDebt(1_000_000m, 600_000m, 100_000m, 100_000m, 50_000m, 20_000m) .Should().Be(430_000m); } // ── Защита от переплаты ────────────────────────────────────────────── [Theory] [InlineData(0, 1000, 1000, true)] // первая полная оплата [InlineData(400, 600, 1000, true)] // добивка ровно до total [InlineData(400, 601, 1000, false)] // чуть больше остатка — переплата [InlineData(1000, 0.00005, 1000, true)] // в пределах ε public void CanApplyPayment_guards_overpay(decimal payed, decimal add, decimal total, bool ok) => MoneyMath.CanApplyPayment(payed, add, total).Should().Be(ok); // ── Права роли ─────────────────────────────────────────────────────── [Fact] public void RolePermissions_All_includes_money_flags() { var p = RolePermissions.All(); p.MoneyView.Should().BeTrue(); p.MoneyEdit.Should().BeTrue(); p.CashShiftOperate.Should().BeTrue(); } [Fact] public void RolePermissions_default_denies_money() { var p = new RolePermissions(); p.MoneyView.Should().BeFalse(); p.MoneyEdit.Should().BeFalse(); p.CashShiftOperate.Should().BeFalse(); } }