В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача отойти от стандартной автоматической нумерации документов. Типичный пример — необходимость присваивать специфический номер (например, номер уведомления или внутреннего акта) не всем документам подряд, а только выбранным пользователем, и делать это по нажатию кнопки на форме. При этом номер должен соблюдать определенный формат (например, «У001»), быть уникальным в пределах года и автоматически увеличиваться.
В данной статье мы подробно разберем, как реализовать такой функционал в конфигурации «Зарплата и управление персоналом 3.1» (ЗУП), хотя описанные методы универсальны для любой конфигурации на управляемых формах.
Это наиболее прямолинейный способ, который не требует создания дополнительных объектов в метаданных (кроме самого реквизита в документе). Рассмотрим ситуацию: у нас есть документ Отпуск, в который мы добавили реквизит НомерУведомления (тип: Строка). Нам нужно найти самый большой номер, который уже был присвоен в текущем году, извлечь из него числовую часть, прибавить единицу и сформировать новую строку.
Разберем алгоритм по шагам:
Проанализируем пример кода для реализации этой логики:
&НаСервере
Функция ПолучитьСледующийНомерУведомления(Организация, ДатаДокумента)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МАКСИМУМ(Отпуск.НомерУведомления) КАК МаксНомер
|ИЗ
| Документ.Отпуск КАК Отпуск
|ГДЕ
| Отпуск.Организация = &Организация
| И Отпуск.Дата МЕЖДУ &НачалоГода И &КонецГода
| И Отпуск.НомерУведомления ПОДОБНО ""У%""";
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("НачалоГода", НачалоГода(ДатаДокумента));
Запрос.УстановитьПараметр("КонецГода", КонецГода(ДатаДокумента));
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() И ЗначениеЗаполнено(Выборка.МаксНомер) Тогда
// Извлекаем цифры, предполагая формат "У001"
ТекстНомера = СокрЛП(Выборка.МаксНомер);
ЧисловаяЧасть = Сред(ТекстНомера, 2); // Отсекаем префикс "У"
НовыйНомерЧислом = Число(ЧисловаяЧасть) + 1;
Иначе
НовыйНомерЧислом = 1;
EndЕсли;
// Формируем строку с лидирующими нулями (например, У005)
Возврат "У" + Формат(НовыйНомерЧислом, "ЧГ=0; ЧЦ=3; ЧВН=");
КонецФункции
В данном примере мы используем функцию МАКСИМУМ в языке запросов 1С. Важно помнить, что если поле строковое, то МАКСИМУМ может работать некорректно, если количество знаков в номере меняется (например, "У10" может оказаться меньше, чем "У9" при строковом сравнении). Поэтому крайне желательно использовать формат с лидирующими нулями: "У001", "У002" и так далее.
Если документов в базе очень много (десятки тысяч), постоянное выполнение запроса МАКСИМУМ по таблице документов может замедлить работу системы. Более надежным и производительным решением будет использование независимого регистра сведений для хранения последнего выданного номера.
Рассмотрим структуру такого регистра (назовем его СчетчикиНомеровУведомлений):
Организация (тип: СправочникСсылка.Организации).Год (тип: Число, длина 4).ПоследнийНомер (тип: Число).Преимущество этого метода в том, что нам не нужно перебирать документы. Мы просто обращаемся к одной записи регистра, получаем число, увеличиваем его и записываем обратно. Это гарантирует высокую скорость работы.
Посмотрим на пример кода с использованием регистра:
&НаСервере
Функция СгенерироватьНомерЧерезРегистр(Организация, ДатаДокумента)
ТекущийГод = Год(ДатаДокумента);
// Устанавливаем блокировку для предотвращения дублей при одновременной работе
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СчетчикиНомеровУведомлений");
ЭлементБлокировки.УстановитьПараметр("Организация", Организация);
ЭлементБлокировки.УстановитьПараметр("Год", ТекущийГод);
Блокировка.Заблокировать();
МенеджерЗаписи = РегистрыСведений.СчетчикиНомеровУведомлений.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Организация = Организация;
МенеджерЗаписи.Год = ТекущийГод;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
НовыйНомер = МенеджерЗаписи.ПоследнийНомер + 1;
Иначе
НовыйНомер = 1;
МенеджерЗаписи.Организация = Организация;
МенеджерЗаписи.Год = ТекущийГод;
КонецЕсли;
МенеджерЗаписи.ПоследнийНомер = НовыйНомер;
МенеджерЗаписи.Записать();
Возврат "У" + Формат(НовыйНомер, "ЧГ=0; ЧЦ=3; ЧВН=");
КонецФункции
Проанализируем критически важный момент: проблему конкурентного доступа. Если два кадровика одновременно нажмут кнопку «Присвоить номер», система может выдать им один и тот же номер, так как на момент чтения из базы ни один из них еще не успел записать свой документ или обновить регистр.
Чтобы этого избежать, мы использовали объект БлокировкаДанных в предыдущем примере. Рассмотрим подробнее, как это работает. Когда первый пользователь инициирует процесс получения номера, система «бронирует» запись в регистре или определенный диапазон данных. Второй пользователь в этот момент переходит в состояние ожидания и получит доступ к коду только после того, как первый завершит транзакцию. Это единственный способ обеспечить 100% уникальность в многопользовательской среде.
Поскольку ЗУП 3.1 построена на базе Библиотеки стандартных подсистем (БСП), мы можем использовать готовые инструменты для работы с префиксами и строками. Выясним, какие функции могут быть нам полезны.
Для работы с номерами в БСП часто используются общие модули, такие как ПрефиксацияОбъектовКлиентСервер или ПрефиксацияОбъектовСлужебный. Вместо того чтобы вручную обрезать строки через Сред() или Лев(), можно использовать встроенные механизмы, которые корректно обрабатывают префиксы организаций и информационных баз.
Пример использования БСП для дополнения номера нулями:
ПрефиксацияОбъектовКлиентСервер.ДополнитьСтрокуЛП(НовыйНомер, 3, "0")
Разберем, как лучше всего встроить этот функционал в типовую конфигурацию, чтобы не возникло проблем с обновлениями:
Если ЗначениеЗаполнено(Объект.НомерУведомления) Тогда
Ответ = Вопрос("Номер уже присвоен. Перезаписать?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Нет Тогда
Возврат;
КонецЕсли;
КонецЕсли;
Таким образом, мы рассмотрели несколько вариантов решения задачи: от простого запроса к документам до создания надежной системы на базе регистров сведений и блокировок. Выбор метода зависит от нагрузки на вашу базу данных и требований к надежности нумерации.
Важный совет: При реализации нумерации в формате «Уnnn» всегда следите за тем, чтобы длина числовой части была фиксированной (например, три знака). Это позволит избежать ошибок при сортировке и поиске максимального значения встроенными средствами платформы 1С.