food-market/tests/food-market.UnitTests/WeightedBarcodeTests.cs
nns d0fee10e96 fix(barcode): весовой разбор выкл по умолчанию; поиск по ШК — точное совпадение
Реальный сценарий владельца: весов с печатью этикеток нет, штрихкоды весовых —
обычные коды «27xxxxx» в карточке. Исправлены два бага:
- поиск по ШК матчил весовой PLU по АРТИКУЛУ (вода арт.12345 → 1,5 кг) — убрано,
  поиск по штрихкоду больше нигде не матчит по артикулу;
- разбор весовых этикеток был дефолт-ON (WeightedBarcodeSettings.Enabled,
  Organization.WeightBarcodeEnabled) и ловил обычные коды — теперь дефолт OFF.

- ByBarcode: сначала точное совпадение штрихкода (вкл. короткие «27xxxxx»),
  разбор этикетки — только если включён в орг И точного ШК нет.
- Phase40: WeightBarcodeEnabled DEFAULT false + выключение у существующих орг;
  WeightedCodeFormat('short'|'ean13')/WeightedCodePrefix(27)/WeightedCodeDigits(5).
- Генератор ШК нового весового товара: короткий «<prefix>+N цифр» (27xxxxx, next-free)
  или EAN-13 по настройке; уникальность.
- Настройки орг отдают/принимают формат кода весовых.
- Sync отдаёт unitShort («шт»/«кг»/«л») рядом с unitCode (ОКЕИ) — shared UnitShort.
- Тесты: MobileApi (exact-only по умолчанию; не матчит артикул; PLU как ШК при вкл) 9/9;
  unit WeightedBarcode/UnitShort 16/16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:02:43 +05:00

134 lines
5.3 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 FluentAssertions;
using foodmarket.Domain.Organizations;
using foodmarket.Shared.Barcodes;
using Xunit;
namespace foodmarket.UnitTests;
public class WeightedBarcodeTests
{
[Fact]
public void Normal_barcode_passes_through()
{
var r = WeightedBarcode.Parse("4870204010101");
r.IsWeighted.Should().BeFalse();
r.Lookup.Should().Be("4870204010101");
r.WeightKg.Should().BeNull();
r.Price.Should().BeNull();
}
// По умолчанию (решение владельца 09.10) разбор этикеток ВЫКЛЮЧЕН: без явно
// включённых настроек весоподобный код трактуется как обычный штрихкод.
private static readonly WeightedBarcodeSettings On = WeightedBarcodeSettings.Default with { Enabled = true };
[Fact]
public void Default_settings_are_disabled()
{
WeightedBarcodeSettings.Default.Enabled.Should().BeFalse();
var r = WeightedBarcode.Parse("2112345015007");
r.IsWeighted.Should().BeFalse("по умолчанию разбор этикеток выключен");
r.Lookup.Should().Be("2112345015007");
}
[Fact]
public void Weight_prefix_extracts_plu_and_kg()
{
// 21 | 12345 | 01500 | K → PLU 12345, вес 1.500 кг (при включённом разборе)
var r = WeightedBarcode.Parse("2112345015007", On);
r.IsWeighted.Should().BeTrue();
r.ValueKind.Should().Be(WeightedValueKind.WeightKg);
r.Lookup.Should().Be("12345");
r.WeightKg.Should().Be(1.500m);
r.Price.Should().BeNull();
}
[Fact]
public void Price_prefix_extracts_plu_and_price()
{
// 25 | 54321 | 12345 | K → PLU 54321, цена 123.45 (при включённом разборе)
var r = WeightedBarcode.Parse("2554321123456", On);
r.IsWeighted.Should().BeTrue();
r.ValueKind.Should().Be(WeightedValueKind.Price);
r.Lookup.Should().Be("54321");
r.Price.Should().Be(123.45m);
r.WeightKg.Should().BeNull();
}
[Fact]
public void Disabled_settings_treats_weighted_as_plain()
{
var settings = WeightedBarcodeSettings.Default with { Enabled = false };
var r = WeightedBarcode.Parse("2112345015007", settings);
r.IsWeighted.Should().BeFalse();
r.Lookup.Should().Be("2112345015007");
}
[Fact]
public void Custom_prefixes_and_code_digits_honored()
{
// Префикс 28 как весовой, код 6 цифр: 28 | 123456 | 999 | K
var settings = WeightedBarcodeSettings.Create(
enabled: true, weightCsv: "28", priceCsv: "", codeDigits: 6);
var r = WeightedBarcode.Parse("2812345699990", settings);
r.IsWeighted.Should().BeTrue();
r.ValueKind.Should().Be(WeightedValueKind.WeightKg);
r.Lookup.Should().Be("123456");
// value digits = остаток до контрольной: позиции 8..11 = "9999" → 9.999 кг
r.WeightKg.Should().Be(9.999m);
}
[Fact]
public void Create_from_csv_defaults_on_blank()
{
var s = WeightedBarcodeSettings.Create(true, null, "", 0);
s.WeightPrefixes.Should().Contain(new[] { 20, 21, 22, 23, 24 });
s.PricePrefixes.Should().Contain(new[] { 25, 26, 27, 28, 29 });
s.CodeDigits.Should().Be(5);
}
}
public class RolePermissionsEffectiveTests
{
[Fact]
public void SuperAdmin_gets_everything_even_in_simple_mode()
{
var p = RolePermissions.Effective(null, isSuperAdmin: true, isAdmin: false, advancedMode: false);
p["ProductsEdit"].Should().BeTrue();
p["DemandsView"].Should().BeTrue("SuperAdmin обходит простой режим");
p["MoneyEdit"].Should().BeTrue();
}
[Fact]
public void Admin_in_simple_mode_loses_advanced_only()
{
var p = RolePermissions.Effective(null, isSuperAdmin: false, isAdmin: true, advancedMode: false);
p["ProductsEdit"].Should().BeTrue("базовые права у Admin есть");
p["DemandsView"].Should().BeFalse("advanced-only скрыт в простом режиме");
p["TransferEdit"].Should().BeFalse();
p["PriceTypesManage"].Should().BeFalse();
p["StocksView"].Should().BeTrue("не advanced-only");
}
[Fact]
public void Admin_in_advanced_mode_keeps_advanced()
{
var p = RolePermissions.Effective(null, isSuperAdmin: false, isAdmin: true, advancedMode: true);
p["DemandsView"].Should().BeTrue();
p["LoyaltyManage"].Should().BeTrue();
}
[Fact]
public void Plain_role_reflects_its_flags_and_mode()
{
var role = new RolePermissions { ProductsView = true, StocksView = true, DemandsView = true };
var simple = RolePermissions.Effective(role, false, false, advancedMode: false);
simple["ProductsView"].Should().BeTrue();
simple["StocksView"].Should().BeTrue();
simple["DemandsView"].Should().BeFalse("advanced-only в простом режиме");
simple["ProductsEdit"].Should().BeFalse("флаг не выдан роли");
var advanced = RolePermissions.Effective(role, false, false, advancedMode: true);
advanced["DemandsView"].Should().BeTrue();
}
}