using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Xunit;
namespace foodmarket.IntegrationTests;
[Collection(ApiCollection.Name)]
public class ProfitReportTests
{
private readonly ApiFactory _factory;
public ProfitReportTests(ApiFactory factory) => _factory = factory;
private static string RandomBarcode()
=> string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10)));
/// Купили 10 шт по 50 (cost после Supply post = 50), продали 4 шт по
/// 100. Прибыль = 400 − 200 = 200, маржа = 50%.
[Fact]
public async Task Profit_simple_supply_sale()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"prft-{Guid.NewGuid():N}");
var refs = await api.LoadRefsAsync();
var supplierId = await api.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
var p1 = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 100m, RandomBarcode());
// Приёмка: 10 шт по 50.
var sup = await api.Http.PostAsJsonAsync("/api/purchases/supplies", new {
date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId,
notes = "init", lines = new[] { new { productId = p1, quantity = 10m, unitPrice = 50m } } });
sup.EnsureSuccessStatusCode();
var supId = (await sup.Content.ReadFromJsonAsync()).GetProperty("id").GetString();
(await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{supId}/post", new { })).EnsureSuccessStatusCode();
// Продажа: 4 шт по 100.
var sale = await api.Http.PostAsJsonAsync("/api/sales/retail", new {
date = DateTime.UtcNow, storeId = refs.StoreId, retailPointId = (string?)null,
customerId = (string?)null, currencyId = refs.CurrencyId, payment = 0, paidCash = 400m, paidCard = 0m,
lines = new[] { new { productId = p1, quantity = 4m, unitPrice = 100m, discount = 0m, vatPercent = 12m } },
notes = "sale" });
sale.EnsureSuccessStatusCode();
var sid = (await sale.Content.ReadFromJsonAsync()).GetProperty("id").GetString();
(await api.Http.PostAsJsonAsync($"/api/sales/retail/{sid}/post", new { })).EnsureSuccessStatusCode();
var rep = await api.GetJsonAsync("/api/reports/profit?groupBy=product");
var r = rep.EnumerateArray().First(x => x.GetProperty("key").GetString() == p1);
r.GetProperty("revenue").GetDecimal().Should().Be(400m);
r.GetProperty("cost").GetDecimal().Should().Be(200m);
r.GetProperty("profit").GetDecimal().Should().Be(200m);
r.GetProperty("marginPercent").GetDecimal().Should().Be(50m);
}
/// Защита от деления на ноль: при нулевой выручке (продажи=0) margin=0,
/// а не NaN/Infinity. Проверяем что хотя бы пустой набор возвращается без 500.
[Fact]
public async Task Empty_period_returns_no_rows_no_division_by_zero()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"prft-emp-{Guid.NewGuid():N}");
var pastFrom = DateTime.UtcNow.AddYears(-5).ToString("o");
var pastTo = DateTime.UtcNow.AddYears(-4).ToString("o");
var rep = await api.GetJsonAsync($"/api/reports/profit?groupBy=product&from={Uri.EscapeDataString(pastFrom)}&to={Uri.EscapeDataString(pastTo)}");
rep.EnumerateArray().Should().BeEmpty();
}
[Fact]
public async Task Tenant_isolation_profit()
{
var a = new ApiActor(_factory.CreateClient());
var b = new ApiActor(_factory.CreateClient());
await a.SignupAndLoginAsync($"prft-iso-a-{Guid.NewGuid():N}");
await b.SignupAndLoginAsync($"prft-iso-b-{Guid.NewGuid():N}");
var refsA = await a.LoadRefsAsync();
var supplierA = await a.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
var pA = await a.CreateProductAsync(refsA, $"PA-{Guid.NewGuid():N}", 100m, RandomBarcode());
var sup = await a.Http.PostAsJsonAsync("/api/purchases/supplies", new {
date = DateTime.UtcNow, supplierId = supplierA, storeId = refsA.StoreId, currencyId = refsA.CurrencyId,
notes = "init", lines = new[] { new { productId = pA, quantity = 5m, unitPrice = 20m } } });
sup.EnsureSuccessStatusCode();
var supId = (await sup.Content.ReadFromJsonAsync()).GetProperty("id").GetString();
(await a.Http.PostAsJsonAsync($"/api/purchases/supplies/{supId}/post", new { })).EnsureSuccessStatusCode();
var sale = await a.Http.PostAsJsonAsync("/api/sales/retail", new {
date = DateTime.UtcNow, storeId = refsA.StoreId, retailPointId = (string?)null,
customerId = (string?)null, currencyId = refsA.CurrencyId, payment = 0, paidCash = 100m, paidCard = 0m,
lines = new[] { new { productId = pA, quantity = 1m, unitPrice = 100m, discount = 0m, vatPercent = 12m } },
notes = "sale" });
sale.EnsureSuccessStatusCode();
var sid = (await sale.Content.ReadFromJsonAsync()).GetProperty("id").GetString();
(await a.Http.PostAsJsonAsync($"/api/sales/retail/{sid}/post", new { })).EnsureSuccessStatusCode();
var repB = await b.GetJsonAsync("/api/reports/profit?groupBy=product");
repB.EnumerateArray().Should().NotContain(x => x.GetProperty("key").GetString() == pA);
}
}