Переход от ручного учета документов в текстовых редакторах (например, Microsoft Word) к автоматизированной системе на базе платформы 1С:Предприятие часто сопровождается желанием перенести привычные бизнес-процессы "как есть". Один из таких процессов — сквозная нумерация договоров или иных печатных форм. В этой статье мы подробно разберем, как правильно реализовать данный механизм с точки зрения архитектуры 1С, избежав при этом критических ошибок блокировок и дублирования номеров.
При проектировании системы автоматического присвоения номеров начинающие разработчики часто совершают классическую ошибку: пытаются генерировать и увеличивать номер "на лету" непосредственно в момент формирования печатной формы (в модуле менеджера или модуле объекта при формировании табличного документа). Рассмотрим, почему такой подход категорически не рекомендуется.
Система печати операционной системы работает полностью независимо от платформы 1С. Когда пользователь нажимает кнопку "Печать", 1С лишь формирует табличный документ и отправляет его на принтер или выводит на экран в режиме предварительного просмотра. Платформа штатными механизмами не может гарантировать, что документ действительно был успешно распечатан на бумаге (например, у принтера могла закончиться бумага, замяться лист, или пользователь просто закрыл окно предварительного просмотра, решив не печатать документ).
Если генерировать номер в момент печати, мы столкнемся со следующими проблемами:
Правильное решение: Сквозной номер должен присваиваться самому объекту базы данных (документу или элементу справочника "Договоры контрагентов") при его непосредственной записи или проведении в базе данных (в обработчике события ПередЗаписью). Печатная форма должна выполнять исключительно роль отображения данных и лишь считывать уже сохраненный в реквизите объекта номер.
Если сквозная нумерация требуется для нескольких различных видов документов (например, "Договор услуг", "Договор поставки", "Дополнительное соглашение"), платформа 1С предоставляет готовый объект метаданных — Нумераторы.
Разберем по шагам, как настроить этот механизм:
НумераторДоговоров.НумераторДоговоров.Платформа автоматически возьмет на себя контроль уникальности и последовательную раздачу номеров для всех связанных документов на уровне СУБД, исключая дубли при параллельной работе пользователей.
Если мы работаем в современных типовых конфигурациях (например, "1С:Бухгалтерия 8" ред. 3.0, "1С:ERP" или "1С:Комплексная автоматизация"), нам не обязательно писать программный код для нумерации договоров контрагентов. Рассмотрим, как включить эту опцию в пользовательском режиме:
Система самостоятельно настроит нумерацию справочника "Договоры контрагентов" по порядку в рамках календарного года. При создании нового договора поле "Номер" будет автоматически заполнено очередным свободным значением при записи.
Иногда бизнес-логика требует сложного распределения номеров (например, сквозная нумерация должна вестись отдельно для каждой организации, подразделения или конкретного типа договора). Использование простых констант для хранения "последнего номера" крайне нежелательно, так как при высокой интенсивности записи документов пользователи будут постоянно сталкиваться с блокировками базы данных и ошибками времени ожидания.
Проанализируем оптимальное техническое решение с использованием независимого регистра сведений и механизма управляемых блокировок платформы 1С.
Создадим независимый регистр сведений СчетчикНомеровДоговоров со следующей структурой:
Организация (Тип: СправочникСсылка.Организации).ПоследнийНомер (Тип: Число).Разберем программный код, который необходимо разместить в модуле объекта договора (или документа) в обработчике события ПередЗаписью. Для исключения ситуации, когда два пользователя одновременно получают один и тот же "последний" номер, мы применим управляемую блокировку данных:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Нумерацию выполняем только для новых объектов
Если ЭтоНовый() Тогда
НачатьТранзакцию();
Попытка
// 1. Установка управляемой блокировки на запись регистра сведений
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СчетчикНомеровДоговоров");
ЭлементБлокировки.УстановитьПараметр("Организация", ЭтотОбъект.Организация);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// 2. Чтение текущего номера из регистра
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Счетчик.ПоследнийНомер КАК ПоследнийНомер
|ИЗ
| РегистрСведений.СчетчикНомеровДоговоров КАК Счетчик
|ГДЕ
| Счетчик.Организация = &Организация";
Запрос.УстановитьПараметр("Организация", ЭтотОбъект.Организация);
РезультатЗапроса = Запрос.Выполнить();
НовыйНомер = 1;
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
НовыйНомер = Выборка.ПоследнийНомер + 1;
КонецЕсли;
// 3. Запись нового значения счетчика в регистр сведений
МенеджерЗаписи = РегистрыСведений.СчетчикНомеровДоговоров.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Организация = ЭтотОбъект.Организация;
МенеджерЗаписи.ПоследнийНомер = НовыйНомер;
МенеджерЗаписи.Записать(Истина);
// 4. Присвоение номера текущему объекту
ЭтотОбъект.Номер = ФорматироватьНомер(НовыйНомер);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьВЖурналРегистрации("Ошибка генерации номера договора: " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
Отказ = Истина;
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Этот алгоритм на 100% страхует систему от появления дублирующих номеров. Блокировка накладывается исключительно на конкретную Организацию, что позволяет пользователям других филиалов беспрепятственно записывать свои документы параллельно.
В базе данных 1С номера часто хранятся в строковом формате со служебными префиксами и лидирующими нулями (например, МСК00-000412). Выводить такой системный номер в печатную форму договора клиенту неэстетично.
Для приведения номера к человекочитаемому виду воспользуемся стандартной функцией из Библиотеки стандартных подсистем (БСП):
// Пример вывода номера в макет печатной формы
НомерНаПечать = ПрефиксацияОбъектовКлиентСервер.ПолучитьНомерНаПечать(ЭтотОбъект.Номер, Истина, Истина);
// Результат выполнения функции для номера "МСК00-000412" будет: "412"
Параметры функции позволяют гибко управлять удалением префиксов организаций, префиксов информационных баз и отсечением лидирующих нулей, гарантируя красивое представление номера на бумаге.
Специфика учета договоров часто требует ведения нумерации дополнительных соглашений, жестко привязанных к основному договору. В таких случаях используется формат номера [НомерДоговора]/[ПорядковыйНомерДопСоглашения] (например, 412/1, 412/2).
Рекомендуем реализовывать эту задачу через подсчет уже существующих соглашений по конкретному договору. Посмотрим на пример запроса, который вычисляет порядковый номер для нового дополнительного соглашения:
Функция ПолучитьНовыйНомерДопСоглашения(ОсновнойДоговор) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ДопСоглашения.Ссылка) КАК ВсегоСоглашений
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДопСоглашения
|ГДЕ
| ДопСоглашения.ГоловнойДоговор = &ОсновнойДоговор
| И ДопСоглашения.ЭтоДополнительноеСоглашение = ИСТИНА";
Запрос.УстановитьПараметр("ОсновнойДоговор", ОсновнойДоговор);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
Возврат СокрЛП(ОсновнойДоговор.Номер) + "/" + Строка(Выборка.ВсегоСоглашений + 1);
Иначе
Возврат СокрЛП(ОсновнойДоговор.Номер) + "/1";
КонецЕсли;
КонецФункции
Таким образом, мы детально разобрали как организационные, так и технические подходы к решению задачи сквозной нумерации в экосистеме 1С. Использование вышеописанных практик позволит вам создать отказоустойчивую и быструю систему автоматической нумерации документов, полностью защищенную от блокировок СУБД и появления дублей.