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

Нумератор

Описание и логика

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

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

Сохранение

В идеале, вся работа с нумераторами (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);



© «Битрикс», 2001-2020, «1С-Битрикс», 2020
Наверх