RetailOversellingTests ждал 409 на продажу сверх остатка — устаревшая политика. По решению владельца 08.10 продажа в минус разрешена и создаёт предупреждение об отрицательном остатке. Тест переписан: проведение успешно, остаток уходит в минус, создан negative-stock alert. Плюс обновлён docs/filters-sweep-progress.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
4.2 KiB
C#
80 lines
4.2 KiB
C#
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, "недоплата должна блокировать проведение");
|
||
}
|
||
}
|