Нумератор
Описание и логика
Как очевидно из названия, нумератор отвечает за то, чтобы генерировать номера.
Нумератор получает шаблон номера (содержащий служебные слова) и настройки для слов. Затем у нумератора можно запрашивать следующий номер. Например, если шаблон содержит - {NUMBER} - нумератор каждый раз на запрос следующего номера будет возвращать уникальное последовательное число.
Логика работы
Каждый нумератор содержит в себе генераторы, которые отвечают за парсинг служебных слов в шаблоне номера в зависимости от своих настроек. У генераторов есть привязка к типу нумератора, то есть некоторые генераторы работают только с определенным типом нумератора.
Соответственно, от типа нумератора зависит набор возможных слов для формирования шаблона.
Любой нумератор любого модуля может использовать базовые генераторы - случайный номер, дата, последовательный номер и префикс - и слова
{NUMBER}, {DAY}, {MONTH}, {YEAR}, {RANDOM}, {PREFIX}
Функционал нумератора можно расширить, добавив новые классы генераторов и указав им тип нумератора, с которым они могут работать. Так сделано в модулях sale, crm, documentgenerator.
Нужно создать свой класс генератора, который будет подставлять в шаблон номера какие-то свои специальные слова, например {TIME}
Подписаться на событие RegisterModuleDependences('main', 'onNumberGeneratorsClassesCollect', 'mymodule', 'TimeNumberGenerator', 'onGeneratorClassesCollect');.
Реализация метода onGeneratorClassesCollect уже есть в базовом классе \Bitrix\Main\Numerator\Generator\NumberGenerator, поэтому надо просто создать класс генератора, который может выглядеть вот так:
$this->format];
}
/** @inheritdoc */
public function setConfig($config)
{
// при инициализации из БД мы записываем себе формат
$this->setFromArrayOrDefault('format', $config, '0', 'string');
}
/** @inheritdoc */
public static function getSettingsFields()
{
// для того, чтобы в интерфейсе появилось дополнительное текстовое поле, куда можно будет вбить кастомный формат
// указываем, какие у данного генератора есть настраиваемые параметры
// у нас это просто строка с форматом для функции date()
// поле станет видимым в интерфейсе, когда нажмут на синий кубик со служебным словом генератора ({TIME})
return [
[
'settingName' => 'format',
'type' => 'string',
'default' => 'H:i:s',
'title' => 'formatTitle',
],
];
}
/** @inheritdoc */
public static function getTemplateWordsForParse()
{
// какие служебные слова может парсить данный генератор
// в нашем случае только слово {TIME}
return [
static::getPatternFor(static::TEMPLATE_WORD_TIME),
];
}
/** @inheritdoc */
public static function getTemplateWordsSettings()
{
// это слово будет отображаться в интерфейсе в синем кубике
// тыкая на который в шаблон номера будет подставляться {TIME} (это все заработает само)
return [
static::getPatternFor(static::TEMPLATE_WORD_TIME) => 'time',
];
}
/**
* @return string
*/
public static function getAvailableForType()
{
// для каких типов нумераторов доступен этот генератор
// например, текущее время сделаем доступным абсолютно для всех нумераторов в продукте
return Numerator::NUMERATOR_DEFAULT_TYPE;
}
/** @inheritdoc */
public function parseTemplate($template)
{
// сюда приходит строка из шаблона номера, например
// {NUMBER}/{RANDOM}__{DAY}:{TIME}
// данный генератор умеет парсить слово {TIME}
// остальные кракозябры не трогаем, они будут преобразованы другими генераторами
return str_replace(self::getPatternFor(static::TEMPLATE_WORD_TIME), date($this->format, time()), $template);
}
/** @inheritdoc */
public function validateConfig($config)
{
// тут можно проверить, что пользователь указал верный формат для date()
$result = new Result();
return $result;
}
}
В результате изменится интерфейс - появится новое поле formatTitle и синий кубик time:
Создание
В идеале, вся работа с нумераторами (CRUD) происходит через класс \Bitrix\Main\Numerator\Numerator.
Старайтесь придерживаться этого принципа.
use Bitrix\Main\Numerator\Numerator; $numerator = Numerator::create();
Нумератору нужно указать шаблон номера (состоит из служебных слов (плейсхолдеров)) и настройки для каждого служебного слова в номере (если необходимо). Например, слово {RANDOM} - превратится в рандомные символы; для {RANDOM} можно задать length - число символов в последовательности.
Получить все слова (плейсхолдеры), которые может использовать нумератор типа DOCUMENT
use Bitrix\DocumentGenerator\Driver;
use Bitrix\Main\Numerator\Numerator;
$templateWords = Numerator::getTemplateWordsForType(Driver::NUMERATOR_TYPE);
// Результат
[
'Bitrix_Main_Numerator_Generator_SequentNumberGenerator' => [
'{NUMBER}',
],
'Bitrix_Main_Numerator_Generator_DateNumberGenerator' => [
'{DAY}',
'{MONTH}',
'{YEAR}',
],
'Bitrix_Main_Numerator_Generator_RandomNumberGenerator' => [
'{RANDOM}',
],
'Bitrix_Main_Numerator_Generator_PrefixNumberGenerator' => [
'{PREFIX}',
],
'Bitrix_DocumentGenerator_Integration_Numerator_DocumentNumberGenerator' => [
'{CLIENT_ID}',
'{SELF_ID}',
'{SELF_COMPANY_ID}',
],
];
Для всех типов нумераторов по умолчанию доступны 4 базовых генератора, соответственно, всегда будут доступны слова:
{NUMBER}- последовательное число{DAY}- день месяца в момент генерации номера, с ведущим нулем => 01, 15, ...{MONTH}- номер месяца в момент генерации номера, с ведущим нулем => 03, 11, ...{YEAR}- текущий год, на момент генерации номера нумератором => 2018, ...{RANDOM}- случайный набор символов из латинских букв в верхнем регистре и цифр{PREFIX}- указанный фиксированный набор символов
Документные нумераторы (тип DOCUMENT) могут дополнительно использовать:
{CLIENT_ID}- ID клиента{SELF_ID}- ID сущности, которая является провайдером данных (зависит от того, что будет передано нумератору при формировании номера){SELF_COMPANY_ID}- ID компании
В CRM (для нумераторов типа CRM_QUOTE, CRM_INVOICE) можно дополнительно использовать слова:
{INVOICE_ID}Номер счета{USER_ID_INVOICES_COUNT}- Id пользователя и число его счетов{QUOTE_ID}- Номер предложения{USER_ID_QUOTES_COUNT}- Id пользователя и число его предложений
В магазине (для нумераторов типа ORDER):
{USER_ID_ORDERS_COUNT}- Id пользователя и число его заказов{ORDER_ID}- Номер заказа
Получить все настройки для создания нумератора типа DOCUMENT
use Bitrix\DocumentGenerator\Driver;
use Bitrix\Main\Numerator\Numerator;
$settings = Numerator::getSettingsFields(Driver::NUMERATOR_TYPE);
// Результат
[
'settingsFields' => [
'Bitrix_Main_Numerator_Numerator' => [
['settingName' => 'name', 'type' => 'string', 'default' => 'Нумератор 1', 'title' => 'Название нумератора', ],
['settingName' => 'template', 'type' => 'string', 'title' => 'Шаблон номера',],
],
'Bitrix_Main_Numerator_Generator_SequentNumberGenerator' => [
['settingName' => 'start', 'type' => 'int', 'default' => 1, 'title' => 'Начинать последовательный номер с',],
['settingName' => 'step', 'type' => 'int', 'default' => 1, 'title' => 'Увеличивать последовательный номер на',],
['settingName' => 'periodicBy', 'type' => 'array', 'title' => 'Период работы нумератора', 'values' => [
['settingName' => 'default', 'value' => '', 'title' => 'Постоянно',],
['settingName' => 'day', 'value' => 'day', 'title' => 'В пределах дня',],
['settingName' => 'month', 'value' => 'month', 'title' => 'В пределах месяца',],
['settingName' => 'year', 'value' => 'year', 'title' => 'В пределах года',],],
],
['settingName' => 'timezone', 'type' => 'array', 'values' => [...]],
...,
],
...,
],
'settingsWords' => [
'Bitrix_Main_Numerator_Generator_SequentNumberGenerator' => ['{NUMBER}' => 'Последовательный номер',],
'Bitrix_Main_Numerator_Generator_DateNumberGenerator' => ['{DAY}' => 'Текущий день', '{MONTH}' => 'Текущий месяц', '{YEAR}' => 'Текущий год',],
'Bitrix_Main_Numerator_Generator_RandomNumberGenerator' => ['{RANDOM}' => 'Случайный номер',],
'Bitrix_Main_Numerator_Generator_PrefixNumberGenerator' => ['{PREFIX}' => 'Префикс',],
]
];
settingsWords - собственно, слова для шаблона (Numerator::getTemplateWordsForType('DOCUMENT'))
settingsFields - обычно используются для формирования html формы, поэтому так много указаний. На основании типов настроек генерируется код для инпутов, дропдаунов, заголовки полей формы, заполняются дефолтные значения и т.д.
Сохранение
В идеале, вся работа с нумераторами (CRUD) происходит через класс \Bitrix\Main\Numerator\Numerator.
Старайтесь придерживаться этого принципа.
В итоге, создание нумератора, установка настроек и сохранение выглядят так:
use Bitrix\Main\Numerator\Numerator;
use Bitrix\Main\Numerator\Generator;
$config = [
Numerator::getType() => [
'name' => 'my awesome numerator',
'template' => '{PREFIX}__{YEAR}/{NUMBER}--{RANDOM}',
],
Generator\RandomNumberGenerator::getType() => [
'length' => '6',
],
Generator\SequentNumberGenerator::getType() => [
'start' => '3',
'step' => '2',
],
Generator\PrefixNumberGenerator::getType() => [
'prefix' => 'test',
],
];
$numerator = Numerator::create();
$numerator->setConfig($config);
/** @var \Bitrix\Main\Entity\AddResult $result **/
$result = $numerator->save();
Настройки
use Bitrix\Main\Numerator\Numerator;
use Bitrix\Main\Numerator\Generator;
// Bitrix_Main_Numerator_Numerator
Numerator::getType() => [
[
'name', // Название нумератора - ОБЯЗАТЕЛЬНОЕ ПОЛЕ
'template' // Шаблон номера - ОБЯЗАТЕЛЬНОЕ ПОЛЕ
],
],
// Порядковый номер - {NUMBER}
// Bitrix_Main_Numerator_Generator_SequentNumberGenerator
Generator\SequentNumberGenerator::getType() => [
[
'start', // С какого числа начинать
'step', // Шаг для увеличения номера
'periodicBy', // ['day', 'month', 'year'] - Сбрасывать счетчик в start при наступлении нового периода
'timezone', // По какому часовому поясу определять наступление нового периода (дня и т.д.)
// ['', 'Pacific/Midway', ...] - значения из \CTimeZone::GetZones()
'isDirectNumeration', // boolean, Использовать сквозную нумерацию или иметь независимые счетчики для каждой компании
],
],
// Случайный номер из букв латинского алфавита в верхнем регистре и цифр - {RANDOM}
// Bitrix_Main_Numerator_Generator_RandomNumberGenerator
Generator\RandomNumberGenerator::getType() => [
[
'length', // Длина номера
],
],
// Строка символов - {PREFIX}
// Bitrix_Main_Numerator_Generator_PrefixNumberGenerator
Generator\PrefixNumberGenerator::getType() => [
[
'prefix', // Строка
],
],
// Дата - {DAY}, {MONTH}, {YEAR}
// Bitrix_Main_Numerator_Generator_DateNumberGenerator формат дат на данный момент не настраивается
Получение существующего нумератора по ID
use Bitrix\Main\Numerator\Numerator; $numerator = Numerator::load($numeratorId);
Получение первого существующего нумератора по его типу
$numerator = \Bitrix\Main\Numerator\Numerator::getOneByType('ORDER');
Получение списка нумераторов по типу
$numerator = \Bitrix\Main\Numerator\Numerator::getListByType('ORDER');
Получение следующего номера у нумератора
$nextNumber = $numerator->getNext();
Изменение настроек по ID
В идеале, вся работа с нумераторами (CRUD) происходит через класс \Bitrix\Main\Numerator\Numerator.
Старайтесь придерживаться этого принципа.
use Bitrix\Main\Numerator\Numerator; $numerator = Numerator::load($numeratorId); $config = $numerator->getConfig(); $config[Numerator::getType()]['name'] = 'updated name'; /*** @var \Bitrix\Main\Entity\UpdateResult|Result $result **/ $result = Numerator::update($id, $config);
Передача контекста
В идеале, вся работа с нумераторами (CRUD) происходит через класс \Bitrix\Main\Numerator\Numerator.
Старайтесь придерживаться этого принципа.
Иногда нумератор в общем случае не знает, на что заменить некоторое служебное слово в шаблоне (например, {INVOICE_ID} - номер счета для нумератора в срм). Поэтому, нумератору нужен некий контекст или источник данных. (Для нумераторов, использующих динамически конфигурируемые генераторы, реализующие интерфейс DynamicConfigurable).
Контекст можно передать несколькими способами.
// вторым параметром в Numerator::load \Bitrix\Main\Numerator\Numerator::load($numeratorId, $source = null); // имея нумератор, установить ему контекст $numerator->setDynamicConfig($dynamicConfig);
Хеш нумератора
В идеале, вся работа с нумераторами (CRUD) происходит через класс \Bitrix\Main\Numerator\Numerator.
Старайтесь придерживаться этого принципа.
Нумератор, содержащий служебное слово {NUMBER} в шаблоне, может одновременно отвечать за несколько последовательных номеров (у него будет несколько "внутренних счетчиков"). В общем случае, запрашивая следующий номер у нумератора, мы будем получать последовательно увеличивающееся число.
use Bitrix\Main\Numerator\Numerator;
$numerator = Numerator::create();
$numerator->setConfig([
Numerator::getType() => [
'name' => 'test sequence',
'template' => '{NUMBER}',
],
]);
$result = $numerator->save();
$numerator = Numerator::load($result->getId());
echo $numerator->getNext() . PHP_EOL;
$numerator = Numerator::load($result->getId());
echo $numerator->getNext() . PHP_EOL;
$numerator = Numerator::load($result->getId());
echo $numerator->getNext() . PHP_EOL;
// Получим
// 1
// 2
// 3
Если же нумератору указывать хеш (строку) и запрашивать номер для разного хеша - мы получим независимые счетчики и сможем получать номера у каждого из них отдельно.
use Bitrix\Main\Numerator\Numerator;
$numerator = Numerator::create();
$numerator->setConfig([
Numerator::getType() => [
'name' => 'sequence',
'template' => '{NUMBER}',
],
]);
$result = $numerator->save();
$numerator = Numerator::load($result->getId());
echo $numerator->getNext('A') . PHP_EOL;
$numerator = Numerator::load($result->getId());
echo $numerator->getNext('B') . PHP_EOL;
$numerator = Numerator::load($result->getId());
echo $numerator->getNext('A') . PHP_EOL;
// Получим
// 1
// 1
// 2
Например, если передавать в качестве хеша Id компании, то можно получить независимую генерацию последовательных номеров в шаблоне для разных компаний по одному и тому же шаблону номера.
Хеш можно установить несколькими способами.
(Для нумераторов, использующих генераторы, реализующие интерфейс Sequenceable)
use Bitrix\Main\Numerator\Numerator; // вторым параметром в Numerator::load в виде объекта // реализующего интерфейс Hashable, возвращающего хеш строкой в $hashable->getHash() Numerator::load($numeratorId, $hashable); // передать хеш в getNext в виде строки $hash = 'MANAGER_42'; $numerator->getNext($hash) // установить хеш через сеттер в виде объекта Hashable $numerator->setHash($hashable);