Нумератор
Описание и логика
Как очевидно из названия, нумератор отвечает за то, чтобы генерировать номера.
Нумератор получает шаблон номера (содержащий служебные слова) и настройки для слов. Затем у нумератора можно запрашивать следующий номер. Например, если шаблон содержит - {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);