Найдено Mac-Claude на стенде: 1. Генератор внутреннего EAN-13 давал «02…» (ведущий 0 → зона UPC-A, может совпасть с реальным товаром). InternalBarcode.PickPrefix теперь берёт 2x из restricted- circulation 20–29 (без ведущего нуля), не пересекаясь с весовыми префиксами орг; «02» — только крайний резерв. Юнит-тест обновлён. 2. «Проверка данных» на Демо показывала все 0 и lastCheckedAt=null — ночной cron ещё не отрабатывал после пересева. Добавлен разовый BackgroundJob.Enqueue(DataQualityJob) при старте: после деплоя/пересева счётчики считаются сразу по всем организациям. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
3.6 KiB
C#
60 lines
3.6 KiB
C#
namespace foodmarket.Shared.Barcodes;
|
||
|
||
/// <summary>Генерация внутреннего (внутримагазинного) штрихкода EAN-13 для товаров
|
||
/// без заводского кода. По решению владельца штрихкод обязателен у ЛЮБОГО товара;
|
||
/// если пользователь/импорт не задал — генерируем здесь.
|
||
///
|
||
/// Префикс выбирается так, чтобы НЕ пересекаться с весовыми/ценовыми префиксами
|
||
/// организации (иначе внутренний код распознается кассой как весовая этикетка).
|
||
/// Предпочтительно «02» (GS1 restricted circulation — внутреннее обращение, не
|
||
/// конфликтует с заводскими), иначе первый свободный 2-значный префикс.
|
||
/// Контрольная цифра — стандартная EAN-13.</summary>
|
||
public static class InternalBarcode
|
||
{
|
||
/// <summary>Контрольная цифра EAN-13 по 12 цифрам тела.</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>Подобрать 2-значный префикс внутреннего кода. GS1 restricted circulation
|
||
/// (внутримагазинные) — это 02 и 20–29. НЕ используем «02»: с ведущим нулём 13-значный
|
||
/// код выглядит как UPC-A (зона США) и может совпасть с реальным товаром. Берём 2x
|
||
/// (20–29) — настоящие коды такие префиксы не используют — не пересекаясь с весовыми/
|
||
/// ценовыми префиксами организации (если разбор этикеток включён). Приоритет:
|
||
/// свободный из 20–29 → свободный из 90–99 → 02 (крайний случай).</summary>
|
||
public static int PickPrefix(WeightedBarcodeSettings settings)
|
||
{
|
||
var forbidden = new HashSet<int>();
|
||
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; // всё занято (весьма редкий случай) — крайний резерв
|
||
}
|
||
|
||
/// <summary>Сгенерировать внутренний EAN-13. <paramref name="rnd"/> — источник
|
||
/// случайности (для детерминизма в тестах можно передать свой).</summary>
|
||
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);
|
||
}
|
||
}
|