Документация для разработчиков

Нумератор

Как очевидно из названия, нумератор отвечает за то, чтобы генерировать номера.

Нумератор получает шаблон номера (содержащий служебные слова) и настройки для слов. Затем у нумератора можно запрашивать следующий номер. Например, если шаблон содержит - {NUMBER} - нумератор каждый раз на запрос следующего номера будет возвращать уникальное последовательное число.

Логика работы

Каждый нумератор содержит в себе генераторы, которые отвечают за парсинг служебных слов в шаблоне номера в зависимости от своих настроек. У генераторов есть привязка к типу нумератора, то есть некоторые генераторы работают только с определенным типом нумератора.

Соответственно, от типа нумератора зависит набор возможных слов для формирования шаблона.

Любой нумератор любого модуля может использовать базовые генераторы - случайный номер, дата, последовательный номер и префикс - и слова {NUMBER}, {DAY}, {MONTH}, {YEAR}, {RANDOM}, {PREFIX}

Функционал нумератора можно расширить, добавив новые классы генераторов и указав им тип нумератора, с которым они могут работать. Так сделано в модулях sale, crm, documentgenerator.

Нужно создать свой класс генератора, который будет подставлять в шаблон номера какие-то свои специальные слова, например {TIME}

Подписаться на событие RegisterModuleDependences('main', 'onNumberGeneratorsClassesCollect', 'mymodule', 'TimeNumberGenerator', 'onGeneratorClassesCollect');.

Реализация метода onGeneratorClassesCollect уже есть в базовом классе \Bitrix\Main\Numerator\Generator\NumberGenerator, поэтому надо просто создать класс генератора, который может выглядеть вот так:

<?php
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Numerator\Generator\NumberGenerator;
use Bitrix\Main\Numerator\Generator\Contract\UserConfigurable;
use Bitrix\Main\Numerator\Numerator;

class TimeNumberGenerator extends NumberGenerator implements UserConfigurable
{
	const TEMPLATE_WORD_TIME = "TIME";
	protected $format;
	
    /** @inheritdoc */
	public function getConfig()
	{
        // через интерфейс можно будет указывать формат времени, чтобы сохранить этот формат в базу данных и потом использовать при формировании номера возвращаем массив того, что необходимо сохранить данному генератору
		return ['format' => $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 формы, поэтому так много указаний. На основании типов настроек генерируется код для инпутов, дропдаунов, заголовки полей формы, заполняются дефолтные значения и т.д.

Сохранение нумератора

В итоге, создание нумератора, установка настроек и сохранение выглядят так:

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

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

Передача контекста

Иногда нумератор в общем случае не знает, на что заменить некоторое служебное слово в шаблоне (например, {INVOICE_ID} - номер счета для нумератора в срм). Поэтому, нумератору нужен некий контекст или источник данных. (Для нумераторов, использующих динамически конфигурируемые генераторы, реализующие интерфейс DynamicConfigurable).

Контекст можно передать несколькими способами.

// вторым параметром в Numerator::load
\Bitrix\Main\Numerator\Numerator::load($numeratorId, $source = null);

// имея нумератор, установить ему контекст
$numerator->setDynamicConfig($dynamicConfig);

Хеш нумератора

Нумератор, содержащий служебное слово {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);

Пользовательские комментарии

Мы будем рады, если разработчики добавят свои комментарии по практическому использованию методов системы.

Для этого нужно всего лишь авторизоваться на сайте

Но помните, что Пользовательские комментарии, несмотря на модерацию, не являются официальной документацией. Ответственность за их использование несет сам пользователь.

Также Пользовательские комментарии не являются местом для обсуждения функционала. По подобным вопросам обращайтесь на форумы.
© «Битрикс», 2001-2020, «1С-Битрикс», 2020
Наверх