В процессе разработки на платформе 1С:Предприятие часто возникает задача организовать нумерацию элементов справочника таким образом, чтобы с началом каждого нового календарного года счетчик сбрасывался в единицу. В отличие от документов, где периодичность нумерации настраивается стандартным свойством метаданных, справочники по умолчанию имеют сквозную нумерацию за весь период существования базы. Рассмотрим подробнее, какими способами можно добиться периодичности для справочников, и разберем технические нюансы каждого подхода.
Проанализируем самый простой с точки зрения использования штатных возможностей платформы метод. Платформа 1С позволяет настроить автоматическую нумерацию в пределах подчинения владельцу. Если мы назначим справочнику владельца, то для каждого конкретного элемента-владельца система будет вести отдельный счетчик кодов.
Для реализации этого метода выполним следующие шаги:
Года. В нем будут храниться элементы, соответствующие календарным годам (например, "2023", "2024").Года.Теперь при создании нового элемента нам достаточно программно определить текущий год и подставить соответствующего владельца. Рассмотрим пример кода в модуле объекта целевого справочника в обработчике ПередЗаписью:
Процедура ПередЗаписью(Отказ)
Если ЭтоНовый() И НЕ ЗначениеЗаполнено(Владелец) Тогда
ТекущийГодТекст = Формат(ТекущаяДата(), "ДФ=yyyy");
СсылкаНаГод = Справочники.Года.НайтиПоНаименованию(ТекущийГодТекст);
Если СсылкаНаГод.Пустая() Тогда
// Если года еще нет в справочнике, создадим его
НовыйГод = Справочники.Года.СоздатьЭлемент();
НовыйГод.Наименование = ТекущийГодТекст;
НовыйГод.Записать();
СсылкаНаГод = НовыйГод.Ссылка;
КонецЕсли;
Владелец = СсылкаНаГод;
КонецЕсли;
КонецПроцедуры
Этот метод хорош тем, что платформа сама контролирует уникальность и последовательность кодов. Однако он кардинально меняет структуру данных: справочник становится подчиненным, что может потребовать переработки существующих запросов и отчетов.
Разберем ситуацию, когда менять структуру справочника нельзя. В этом случае мы можем использовать механизм префиксации. Платформа 1С при генерации нового кода ищет максимальный номер среди кодов с таким же префиксом. Если в начале года мы сменим префикс, нумерация автоматически начнется с единицы.
Проанализируем работу обработчика ПриУстановкеНовогоКода в модуле объекта справочника. Мы будем добавлять текущий год в начало кода в качестве префикса:
Процедура ПриУстановкеНовогоКода(СтандартнаяОбработка, Префикс)
// Формируем префикс на основе текущего года, например "24-"
ПрефиксГода = Формат(ТекущаяДата(), "ДФ=yy") + "-";
Префикс = Префикс + ПрефиксГода;
КонецПроцедуры
Важный момент: Чтобы этот механизм работал корректно, убедитесь, что длина кода справочника достаточна для размещения и префикса, и самого номера. Если в 2024 году платформа встретит код "24-001", а в 2025 году мы установим префикс "25-", система не найдет кодов, начинающихся на "25-", и предложит номер "25-001".
Иногда задача стоит сложнее: нужно нумеровать не стандартный Код, а произвольный строковый или числовой реквизит (например, "ВнутреннийНомер"). В этом случае штатные механизмы платформы не помогут, и нам придется реализовать собственный нумератор. Чтобы избежать медленных запросов МАКСИМУМ к таблице справочника, воспользуемся вспомогательным регистром сведений.
Выясним причину, почему регистр лучше простого запроса: при большом количестве записей запрос ВЫБРАТЬ МАКСИМУМ(ВнутреннийНомер) может замедлять работу системы и вызывать блокировки всей таблицы справочника. Регистр сведений же будет хранить только одну запись для каждого года с актуальным последним номером.
Создадим независимый регистр сведений СчетчикиНомеров с измерениями Год (Число) и ТипОбъекта (Строка), и ресурсом ПоследнийНомер (Число).
Разберем алгоритм получения номера в модуле объекта справочника:
Процедура ПередЗаписью(Отказ)
Если ЭтоНовый() И ВнутреннийНомер = 0 Тогда
ТекущийГод = Год(ТекущаяДата());
// Устанавливаем блокировку для предотвращения дублей в многопользовательском режиме
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СчетчикиНомеров");
ЭлементБлокировки.УстановитьПараметр("Год", ТекущийГод);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Получаем текущее значение счетчика
Отбор = Новый Структура("Год, ТипОбъекта", ТекущийГод, "МойСправочник");
ЗначениеСчетчика = РегистрыСведений.СчетчикиНомеров.Получить(Отбор).ПоследнийНомер;
НовыйНомер = ЗначениеСчетчика + 1;
// Обновляем счетчик в регистре
МенеджерЗаписи = РегистрыСведений.СчетчикиНомеров.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Год = ТекущийГод;
МенеджерЗаписи.ТипОбъекта = "МойСправочник";
МенеджерЗаписи.ПоследнийНомер = НовыйНомер;
МенеджерЗаписи.Записать();
ВнутреннийНомер = НовыйНомер;
КонецЕсли;
КонецПроцедуры
Рассмотрим ситуацию, когда два бухгалтера одновременно нажимают кнопку "Записать". Без использования объекта БлокировкаДанных оба сеанса могут прочитать из базы одно и то же число (например, 100), прибавить к нему единицу и попытаться записать номер 101. Это приведет либо к ошибке уникальности, либо к появлению дублей, если контроль уникальности не настроен. Именно поэтому работа со счетчиками должна происходить внутри транзакции записи с использованием исключительных блокировок.
Посмотрим на проблему пропусков в нумерации. Платформенный механизм 1С "бронирует" номер при открытии формы или вызове метода УстановитьНовыйКод(). Если пользователь передумал и закрыл форму без записи, этот номер может быть потерян, и в последовательности возникнет "дырка". Если для вашей бизнес-логики критична непрерывная нумерация, присваивайте номер в самый последний момент — в событии ПередЗаписью, как показано в примерах выше.
Проанализировав все варианты, можно сделать следующие выводы:
ПриУстановкеНовогоКода. Это стандартный путь для большинства типовых конфигураций.Помните, что любое решение по ручной нумерации требует тщательного тестирования в условиях многопользовательской работы, чтобы исключить риск возникновения дублей или критических блокировок базы данных.