food-market/tests/food-market.IntegrationTests/RetailOversellingTests.cs
nns d7db011331 test(sales): привести oversell-тест к политике «продажа в минус разрешена»
RetailOversellingTests ждал 409 на продажу сверх остатка — устаревшая политика.
По решению владельца 08.10 продажа в минус разрешена и создаёт предупреждение об
отрицательном остатке. Тест переписан: проведение успешно, остаток уходит в минус,
создан negative-stock alert. Плюс обновлён docs/filters-sweep-progress.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 10:01:56 +05:00

80 lines
4.2 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 System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Xunit;
namespace foodmarket.IntegrationTests;
[Collection(ApiCollection.Name)]
public class RetailOversellingTests
{
private readonly ApiFactory _factory;
public RetailOversellingTests(ApiFactory factory) => _factory = factory;
private static string RandomBarcode()
=> string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10)));
// Решение владельца 08.10.2026: продажа в минус РАЗРЕШЕНА (как у МойСклада по
// умолчанию) — вместо блокировки создаётся предупреждение об отрицательном
// остатке. Этот тест раньше ждал 409 (старая политика) и был устаревшим.
[Fact]
public async Task Posting_sale_above_available_stock_is_allowed_and_creates_negative_alert()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"oversell-{Guid.NewGuid():N}");
var refs = await api.LoadRefsAsync();
var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", retailPrice: 100m, RandomBarcode());
// Остатка нет (приёмок не делали). Продаём 5 шт. paidCash покрывает итог,
// чтобы дойти до проверки остатка (а не упасть на валидации оплаты).
var draftResp = 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 = 500m,
paidCard = 0m,
notes = "oversell",
lines = new[] { new { productId, quantity = 5m, unitPrice = 100m, discount = 0m, vatPercent = 0m } },
});
draftResp.EnsureSuccessStatusCode();
var saleId = (await draftResp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
using var post = await api.Http.PostAsJsonAsync($"/api/sales/retail/{saleId}/post", new { });
post.IsSuccessStatusCode.Should().BeTrue(
$"продажа в минус разрешена: {(int)post.StatusCode} {await post.Content.ReadAsStringAsync()}");
// Остаток ушёл в −5, и создано предупреждение об отрицательном остатке.
(await api.StockOfAsync(refs.StoreId, productId)).Should().Be(-5m);
var cnt = await api.GetJsonAsync("/api/negative-stock/count");
cnt.GetProperty("total").GetInt32().Should().BeGreaterThan(0, "минус создаёт предупреждение");
}
[Fact]
public async Task Posting_sale_with_underpayment_is_rejected()
{
var api = new ApiActor(_factory.CreateClient());
await api.SignupAndLoginAsync($"underpay-{Guid.NewGuid():N}");
var refs = await api.LoadRefsAsync();
var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", 100m, RandomBarcode());
// Итог 200, оплачено 100 → недоплата → 400 (до проверки остатка).
var draftResp = 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 = 100m, paidCard = 0m, notes = "underpay",
lines = new[] { new { productId, quantity = 2m, unitPrice = 100m, discount = 0m, vatPercent = 0m } },
});
draftResp.EnsureSuccessStatusCode();
var saleId = (await draftResp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
using var post = await api.Http.PostAsJsonAsync($"/api/sales/retail/{saleId}/post", new { });
((int)post.StatusCode).Should().Be(400, "недоплата должна блокировать проведение");
}
}