Мы часто сталкиваемся с ситуациями, когда требуется присвоить уникальный порядковый номер записям в регистре сведений. По своей природе регистры сведений предназначены для хранения информации, зависящей от комбинации измерений, и не обладают встроенными механизмами для сквозной нумерации, как, например, документы. Однако, в реальной практике, особенно когда изменение архитектуры на использование документов невозможно из-за уже сложившейся структуры или других ограничений, возникает необходимость реализовать такую нумерацию. Рассмотрим, как мы можем подойти к решению этой задачи.
Прежде чем углубляться в реализацию, давайте проанализируем ситуацию. Как было отмечено в сообщении (4) Если тебе понадобился номер — значит тебе нужен не регистр, а документ.", обычно для задач с нумерацией, датой, проведением и сложной логикой используются документы. Это обусловлено тем, что документы в 1С имеют встроенные механизмы для работы с последовательностями номеров, журналом документов, проведением и т.д. Однако, в нашей ситуации (сообщение 5: "Да, понимаю, но структуру менять нельзя."), мы вынуждены работать с регистром сведений.
Основная проблема, которую мы хотим решить, — это присвоение каждой новой записи регистра уникального и последовательного номера. При этом важно учесть несколько аспектов:
Множество мест создания записей: Записи могут создаваться из различных форм, обработок или фоновых заданий (сообщение 0: "Мест создания записей много, там менять не вариант."). Это означает, что логика присвоения номера должна быть централизована.
Многопользовательская среда: В системе может работать несколько пользователей одновременно, создавая записи. Мы должны исключить присвоение одного и того же номера разным записям.
Удаление записей: Если записи будут удаляться, это приведет к "дырам" в нумерации (сообщение 1: "А если запись будет удалена - твои порядковые номера накрываются медным тазиком..."). Необходимо понимать, допустимы ли такие пропуски.
Нумерация в разрезе измерений: Возможно, потребуется нумерация не сквозная по всему регистру, а в разрезе определенных измерений (например, отдельная нумерация для каждой организации или каждого года).
Наиболее подходящим и централизованным местом для реализации логики присвоения номера является подписка на событие "Перед записью набора" регистра сведений. Это позволяет нам перехватывать процесс записи данных в регистр независимо от того, откуда он был инициирован (сообщения 3 и 8). Таким образом, мы можем гарантировать, что каждая записываемая запись будет обработана нашим алгоритмом нумерации.
Создание подписки на событие:
Нам необходимо создать новый объект конфигурации "Подписка на событие". В качестве источника события укажем наш регистр сведений. В качестве события выберем "ПередЗаписьюНабора" (или "ПриЗаписи" для записи одной записи, но "ПередЗаписьюНабора" более универсален, так как записи часто пишутся наборами). Затем создадим обработчик события в общем модуле.
Хранение последнего номера:
Чтобы получить следующий порядковый номер, нам нужно где-то хранить последний присвоенный номер. Для этого мы можем использовать:
Константу: Это самый простой способ для сквозной нумерации без разрезов. Создадим константу, например, "ПоследнийНомерРегистра_МойРегистр" с типом Число.
// Пример получения значения константы
ПоследнийНомер = Константы.ПоследнийНомерРегистра_МойРегистр.Получить();
Вспомогательный регистр сведений: Если нам нужна нумерация в разрезе каких-либо измерений (например, по организации, году, типу документа), то лучше создать отдельный вспомогательный регистр сведений, который будет хранить последний номер для каждой комбинации измерений. Измерения этого вспомогательного регистра должны совпадать с измерениями, по которым нам нужна отдельная нумерация в основном регистре. Например, для нумерации по организации и году, вспомогательный регистр может иметь измерения "Организация" и "Год", а также ресурс "ПоследнийНомер".
// Пример получения последнего номера из вспомогательного регистра
// (если нумерация по организации)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ МАКСИМУМ(ПоследниеНомера.Номер) КАК ПоследнийНомер
|ИЗ
| РегистрСведений.ВспомогательныеНомераРегистров КАК ПоследниеНомера
|ГДЕ
| ПоследниеНомера.ИмяРегистра = &ИмяРегистра
| И ПоследниеНомера.Организация = &Организация";
Запрос.УстановитьПараметр("ИмяРегистра", "МойРегистр");
Запрос.УстановитьПараметр("Организация", ЗаписьРегистра.Организация); // Пример
Результат = Запрос.Выполнить().Выбрать();
Если Результат.Следующий() Тогда
ПоследнийНомер = Результат.ПоследнийНомер;
Иначе
ПоследнийНомер = 0;
КонецЕсли;
Важно! В случае вспомогательного регистра, при записи нового номера, мы должны записывать новый номер не как максимум, а как просто инкрементированное значение для соответствующей комбинации измерений.
Обеспечение уникальности и целостности (Транзакции и блокировки):
Это критически важный шаг, особенно в многопользовательской среде. Если несколько пользователей попытаются получить и присвоить номер одновременно, они могут получить один и тот же номер. Для предотвращения этого мы должны использовать транзакции и блокировки.
Механизм следующий:
Мы начинаем транзакцию.
Блокируем данные, хранящие наш последний номер (константу или запись вспомогательного регистра), чтобы никто другой не мог их изменить, пока мы работаем.
Считываем текущий последний номер, увеличиваем его.
Записываем новый увеличенный номер обратно.
Присваиваем этот номер записи(ям) регистра сведений.
Завершаем транзакцию (фиксируем или отменяем, если произошла ошибка).
Пример использования Константы (для сквозной нумерации):
Процедура ПрисвоитьПорядковыйНомерРегиструСведений(Источник, Отказ) Экспорт
// Убедимся, что это наш регистр сведений
Если НЕ Источник.ИмяРегистра = "МойРегистр" Тогда
Возврат;
КонецЕсли;
НачатьТранзакцию();
Попытка
// Блокируем константу, чтобы получить уникальный номер
Константы.ПоследнийНомерРегистра_МойРегистр.Заблокировать();
ПоследнийНомер = Константы.ПоследнийНомерРегистра_МойРегистр.Получить();
Для Каждого ЗаписьРегистра Из Источник.Записи Цикл
ПоследнийНомер = ПоследнийНомер + 1;
ЗаписьРегистра.Номер = ПоследнийНомер; // Предполагаем, что в регистре есть реквизит "Номер"
КонецЦикла;
// Записываем обновленный последний номер
Константы.ПоследнийНомерРегистра_МойРегистр.Установить(ПоследнийНомер);
ФиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Отказ = Истина; // Отменяем запись регистра
Сообщить("Ошибка при присвоении номера: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
В типовой конфигурации "Управление торговлей" (УТ), мы можем найти пример использования константы и транзакций для нумерации в регистре сведений "ЗаданияКЗакрытиюМесяца" (сообщение 11). Вы можете "подсмотреть" там реализацию, чтобы понять принципы работы.
Обработка "дыр" в нумерации:
Как уже говорилось, при удалении записей регистра, номера в последовательности будут иметь пропуски. Если пропуски недопустимы, задача существенно усложняется. Вот несколько возможных подходов, каждый со своими недостатками:
Перенумерация: При удалении записи, все последующие записи перенумеровываются. Это очень ресурсоемкий процесс и может быть неприемлем для большого объема данных или частых удалений.
Менеджер свободных номеров: Хранить список "освободившихся" номеров и использовать их для новых записей. Это усложняет логику получения номера и также может быть неэффективно.
Физическое удаление запрещено: Если бизнес-логика позволяет, вместо физического удаления записи, можно помечать ее как "неактивную" или "удаленную". Номер при этом остается, но запись не используется. Это самый простой способ избежать "дыр".
В большинстве случаев, если речь идет о порядковом номере, а не о жесткой последовательности без пропусков, пропуски допустимы (как и в случае с номерами документов, которые могут быть удалены).
Еще один вариант, предложенный в сообщении 6, — это использование справочника. Суть подхода такова:
Создаем вспомогательный справочник: Например, "НомераРегистра_МойРегистр". В этом справочнике основной реквизит — это Код, который автоматически нумеруется.
При создании записи регистра: Создаем новый элемент этого справочника. Система автоматически присвоит ему уникальный код (который и будет нашим порядковым номером).
Записываем ссылку на элемент справочника в регистр: Добавляем в регистр сведений реквизит, например, "СсылкаНаНомер" с типом СправочникСсылка.НомераРегистра_МойРегистр.
Получаем номер: При необходимости получить порядковый номер записи, мы обращаемся к реквизиту "СсылкаНаНомер" и берем его Код.
Пример (в подписке на событие "ПередЗаписьюНабора"):
Процедура ПрисвоитьНомерЧерезСправочник(Источник, Отказ) Экспорт
Если НЕ Источник.ИмяРегистра = "МойРегистр" Тогда
Возврат;
КонецЕсли;
Для Каждого ЗаписьРегистра Из Источник.Записи Цикл
Если ЗаписьРегистра.СсылкаНаНомер.Пустая() Тогда // Если номер еще не присвоен
НовыйЭлементСправочника = Справочники.НомераРегистра_МойРегистр.СоздатьЭлемент();
НовыйЭлементСправочника.Наименование = "Номер для " + Строка(ТекущаяДата()); // Можно присвоить осмысленное наименование
Попытка
НовыйЭлементСправочника.Записать();
ЗаписьРегистра.СсылкаНаНомер = НовыйЭлементСправочника.Ссылка;
ЗаписьРегистра.Номер = НовыйЭлементСправочника.Код; // Если в регистре есть реквизит "Номер"
Исключение
Отказ = Истина;
Сообщить("Ошибка при создании номера через справочник: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Преимущества: Простота реализации автоматической нумерации. Механизмы уникальности кодов справочника обеспечиваются платформой.
Недостатки: Код справочника обычно имеет строковый тип, что не всегда удобно для числовой сортировки или сравнения. Также, как отмечает сообщение 10, если в будущем понадобится более сложная логика нумерации (например, с префиксами по году или организации), придется сильно перерабатывать этот подход или даже создавать несколько справочников.
Сообщение 7: "может достаточно на форме просто нумеровать?" предлагает вариант, если порядковый номер нужен исключительно для визуального отображения на форме и не должен быть сохранен в базе данных или использоваться для уникальной идентификации. В этом случае, мы можем динамически вычислять номер строки при выводе записей регистра на форму или в отчет. Однако, это не решает задачу присвоения и хранения уникального номера для каждой записи в базе данных, а лишь отображает порядковый номер в текущем списке или отчете. Если требуется, чтобы номер был *частью* данных записи, этот подход не подойдет.
Важно еще раз подчеркнуть (сообщения 4 и 10), что использование регистра сведений для полноценной нумерации, как это делают документы, является обходным путем и может создать ряд проблем в долгосрочной перспективе:
Сложность поддержки: Добавление логики нумерации, транзакций и блокировок делает регистр более сложным объектом, чем он должен быть.
Производительность: Постоянное чтение и запись константы или вспомогательного регистра под транзакцией может создавать узкие места при высокой интенсивности записи.
Отчетность: Регистры сведений оптимизированы для срезов по измерениям, а не для сквозных последовательностей. Построение отчетов, требующих строгой последовательности номеров, может быть затруднено.
Несоответствие назначению: Использование регистра как подобия документа нарушает принципы архитектуры платформы, что может привести к непредсказуемым побочным эффектам или сложностям при обновлении конфигурации.
Если в будущем возникнет возможность пересмотреть архитектуру, настоятельно рекомендуем вернуться к рассмотрению использования документов для таких задач, даже если это будут "легкие" документы с минимумом реквизитов.
Мы проанализировали несколько подходов к присвоению порядкового номера записям регистра сведений в 1С, когда использование документов невозможно. Наиболее надежным и универсальным решением является использование подписки на событие "Перед записью набора", где мы централизованно управляем получением и сохранением следующего номера, используя константу или вспомогательный регистр, а также обязательно применяя транзакции и блокировки для обеспечения уникальности и целостности данных в многопользовательской среде. Мы также рассмотрели альтернативный подход через справочник и обсудили ограничения, связанные с удалением записей и пропуски в нумерации. При выборе решения всегда взвешивайте компромиссы между простотой реализации и потенциальными долгосрочными сложностями.