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); } }