using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Xunit;
namespace foodmarket.IntegrationTests;
/// Схема цен владельца 12.10: закупочная (purchasePrice) = цена строки
/// последней проведённой приёмки; себестоимость — скользящее среднее; продажная
/// меняется приёмкой только при ручной правке в строке. Unpost возвращает
/// закупочную из предыдущей проведённой приёмки.
[Collection(ApiCollection.Name)]
public class SupplyPricingSchemeTests
{
private readonly ApiFactory _factory;
public SupplyPricingSchemeTests(ApiFactory factory) => _factory = factory;
private static string RandomBarcode()
=> string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10)));
private async Task<(decimal? purchase, decimal cost, decimal? retail)> ReadPricesAsync(ApiActor api, string productId, string retailTypeId)
{
var p = await api.GetJsonAsync($"/api/catalog/products/{productId}");
decimal? purchase = p.TryGetProperty("purchasePrice", out var pp) && pp.ValueKind == JsonValueKind.Number ? pp.GetDecimal() : null;
decimal cost = p.GetProperty("cost").GetDecimal();
decimal? retail = null;
foreach (var row in p.GetProperty("prices").EnumerateArray())
if (row.GetProperty("priceTypeId").GetString() == retailTypeId)
retail = row.GetProperty("amount").GetDecimal();
return (purchase, cost, retail);
}
[Fact]
public async Task Supply_without_overrides_sets_purchase_and_cost_keeps_sale_price()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"pp-plain-{Guid.NewGuid():N}");
var refs = await api.LoadRefsAsync();
var supplier = await api.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
var productId = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 200m, RandomBarcode());
// Приёмка 10 шт по 100, без правки продажной цены в строке.
await api.CreatePostedSupplyAsync(refs, supplier, productId, 10m, 100m);
var (purchase, cost, retail) = await ReadPricesAsync(api, productId, refs.PriceTypeId);
purchase.Should().Be(100m, "закупочная = цена строки приёмки");
cost.Should().Be(100m, "себестоимость от нулевого остатка = цена приёмки (скользящее среднее)");
retail.Should().Be(200m, "продажная не меняется, если её не правили в строке");
}
[Fact]
public async Task Supply_with_line_sale_override_updates_card_sale_price()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"pp-override-{Guid.NewGuid():N}");
var refs = await api.LoadRefsAsync();
var supplier = await api.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
var productId = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 200m, RandomBarcode());
var draft = await api.Http.PostAsJsonAsync("/api/purchases/supplies", new
{
date = DateTime.UtcNow, supplierId = supplier, storeId = refs.StoreId, currencyId = refs.CurrencyId,
notes = (string?)null,
lines = new[] { new { productId, quantity = 5m, unitPrice = 90m, retailPriceManuallyOverridden = true, retailPriceOverride = 250m } },
});
draft.EnsureSuccessStatusCode();
var supplyId = (await draft.Content.ReadFromJsonAsync()).GetProperty("id").GetString();
(await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{supplyId}/post", new { })).EnsureSuccessStatusCode();
var (purchase, _, retail) = await ReadPricesAsync(api, productId, refs.PriceTypeId);
purchase.Should().Be(90m);
retail.Should().Be(250m, "продажная обновилась из ручной правки в строке приёмки");
}
[Fact]
public async Task Unpost_restores_purchase_price_from_previous_supply()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"pp-unpost-{Guid.NewGuid():N}");
var refs = await api.LoadRefsAsync();
var supplier = await api.CreateCounterpartyAsync($"S-{Guid.NewGuid():N}");
var productId = await api.CreateProductAsync(refs, $"P-{Guid.NewGuid():N}", 200m, RandomBarcode());
await api.CreatePostedSupplyAsync(refs, supplier, productId, 10m, 100m); // закупочная = 100
var (_, dummyB) = await api.CreatePostedSupplyAsync(refs, supplier, productId, 5m, 120m); // закупочная = 120
_ = dummyB;
// Находим вторую приёмку и отменяем проведение.
var supplies = await api.ListAsync("/api/purchases/supplies?pageSize=200");
var second = supplies.First(s => s.GetProperty("total").GetDecimal() == 600m).GetProperty("id").GetString();
(await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{second}/unpost", new { })).EnsureSuccessStatusCode();
var (purchase, _, _) = await ReadPricesAsync(api, productId, refs.PriceTypeId);
purchase.Should().Be(100m, "после отмены проведения закупочная вернулась из предыдущей приёмки");
}
}