namespace foodmarket.Shared.Barcodes;
/// Генерация внутреннего (внутримагазинного) штрихкода EAN-13 для товаров
/// без заводского кода. По решению владельца штрихкод обязателен у ЛЮБОГО товара;
/// если пользователь/импорт не задал — генерируем здесь.
///
/// Префикс выбирается так, чтобы НЕ пересекаться с весовыми/ценовыми префиксами
/// организации (иначе внутренний код распознается кассой как весовая этикетка).
/// Предпочтительно «02» (GS1 restricted circulation — внутреннее обращение, не
/// конфликтует с заводскими), иначе первый свободный 2-значный префикс.
/// Контрольная цифра — стандартная EAN-13.
public static class InternalBarcode
{
/// Контрольная цифра EAN-13 по 12 цифрам тела.
public static int Ean13CheckDigit(string body12)
{
var sum = 0;
for (int i = 0; i < 12; i++)
{
var d = body12[i] - '0';
sum += i % 2 == 0 ? d : d * 3;
}
return (10 - sum % 10) % 10;
}
/// Подобрать 2-значный префикс внутреннего кода. GS1 restricted circulation
/// (внутримагазинные) — это 02 и 20–29. НЕ используем «02»: с ведущим нулём 13-значный
/// код выглядит как UPC-A (зона США) и может совпасть с реальным товаром. Берём 2x
/// (20–29) — настоящие коды такие префиксы не используют — не пересекаясь с весовыми/
/// ценовыми префиксами организации (если разбор этикеток включён). Приоритет:
/// свободный из 20–29 → свободный из 90–99 → 02 (крайний случай).
public static int PickPrefix(WeightedBarcodeSettings settings)
{
var forbidden = new HashSet();
if (settings.Enabled)
{
foreach (var p in settings.WeightPrefixes) forbidden.Add(p);
foreach (var p in settings.PricePrefixes) forbidden.Add(p);
}
for (int p = 20; p <= 29; p++) if (!forbidden.Contains(p)) return p;
for (int p = 90; p <= 99; p++) if (!forbidden.Contains(p)) return p;
return 2; // всё занято (весьма редкий случай) — крайний резерв
}
/// Сгенерировать внутренний EAN-13. — источник
/// случайности (для детерминизма в тестах можно передать свой).
public static string Generate(WeightedBarcodeSettings? settings = null, Random? rnd = null)
{
settings ??= WeightedBarcodeSettings.Default;
rnd ??= Random.Shared;
var prefix = PickPrefix(settings);
var body = new char[12];
body[0] = (char)('0' + prefix / 10);
body[1] = (char)('0' + prefix % 10);
for (int i = 2; i < 12; i++) body[i] = (char)('0' + rnd.Next(0, 10));
var body12 = new string(body);
return body12 + Ean13CheckDigit(body12);
}
}