В практике автоматизации бизнес-процессов на платформе 1С разработчики часто сталкиваются с нестандартными требованиями со стороны заказчиков или руководителей подразделений. Одно из таких требований — организация независимой нумерации документов для каждого отдельного контрагента или договора. Например, начальник цеха или менеджер по логистике хочет, чтобы отгрузки по Контрагенту А нумеровались по порядку: 1, 2, 3, а по Контрагенту Б параллельно шла своя цепочка номеров: 1, 2, 3.
В этой статье мы подробно проанализируем, почему такое требование конфликтует со стандартными механизмами платформы 1С, рассмотрим риски для бухгалтерского учета и разберем несколько практических вариантов реализации этой задачи с примерами кода.
Прежде чем переходить к программированию, давайте разберем, как устроена автонумерация в 1С. Платформа проектировалась исходя из принципа, что стандартный реквизит Номер (или Код у справочников) является глобально уникальным идентификатором объекта в рамках определенного периода (года, квартала, месяца или без ограничения периода). Если в метаданных документа включено свойство Контроль уникальности, платформа физически заблокирует запись двух документов с одинаковыми номерами за один период.
Попытка отключить контроль уникальности в свойствах документа — это самый опасный путь. В таком случае стандартный механизм автонумерации перестанет работать корректно, а пользователи запутаются при поиске документов. Поэтому нам необходимо найти решение, которое позволит сохранить системную уникальность номеров на уровне платформы, но при этом предоставит пользователям нужную аналитику.
Самый надежный и отказоустойчивый метод с точки зрения архитектуры базы данных — это создание отдельного реквизита для хранения «внутреннего» номера и использование независимого регистра сведений в качестве счетчика. Этот способ исключает возникновение дублей при одновременной работе нескольких пользователей (проблема «гонки данных»).
Рассмотрим по шагам, как реализовать этот механизм:
ВнутреннийНомер (тип: Число или Строка).НумерацияПоКонтрагентам со следующей структурой:
Контрагент (тип: СправочникСсылка.Контрагенты), ПериодГод (тип: Число).ПоследнийНомер (тип: Число).ПередЗаписью документа для генерации нового номера в транзакции с использованием объектной блокировки.Посмотрим на пример кода, который решает задачу транзакционного инкремента номера:
Процедура ПередЗаписьюДокументаПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Проверяем, нужно ли присваивать новый номер
Если Источник.ЭтоНовый() Или НЕ ЗначениеЗаполнено(Источник.ВнутреннийНомер) Тогда
// Установим блокировку для предотвращения одновременного получения одного номера разными сессиями
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НумерацияПоКонтрагентам");
ЭлементБлокировки.УстановитьЗначение("Контрагент", Источник.Контрагент);
ЭлементБлокировки.УстановитьЗначение("ПериодГод", Год(Источник.Дата));
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
// Запрашиваем последний сохраненный номер по данному контрагенту за текущий год
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Нумерация.ПоследнийНомер КАК ПоследнийНомер
|ИЗ
| РегистрСведений.НумерацияПоКонтрагентам КАК Нумерация
|ГДЕ
| Нумерация.Контрагент = &Контрагент
| И Нумерация.ПериодГод = &ПериодГод";
Запрос.УстановитьПараметр("Контрагент", Источник.Контрагент);
Запрос.УстановитьПараметр("ПериодГод", Год(Источник.Дата));
Выборка = Запрос.Выполнить().Выбрать();
НовыйНомер = 1;
Если Выборка.Следующий() Тогда
НовыйНомер = Выборка.ПоследнийНомер + 1;
КонецЕсли;
// Обновляем значение счетчика в регистре сведений
МенеджерЗаписи = РегистрыСведений.НумерацияПоКонтрагентам.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Контрагент = Источник.Контрагент;
МенеджерЗаписи.ПериодГод = Год(Источник.Дата);
МенеджерЗаписи.ПоследнийНомер = НовыйНомер;
МенеджерЗаписи.Записать(Истина);
// Присваиваем вычисленный номер в документ
Источник.ВнутреннийНомер = НовыйНомер;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение "Не удалось сформировать уникальный номер по причине: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Этот способ позволяет сохранить типовую структуру метаданных конфигурации нетронутой. Стандартное поле Номер продолжит заполняться уникальными значениями платформы, а в печатных формах для начальника цеха мы сможем выводить наш красивый ВнутреннийНомер.
Рассмотрим ситуацию, когда стандартный номер все же должен быть уникальным, но пользователи категорически отказываются видеть префиксы в интерфейсе программы. Мы можем использовать стандартный механизм префиксации 1С, но "маскировать" его при показе пользователю.
Разберем этот алгоритм по шагам:
0012-00001.Посмотрим на пример функции очистки номера:
Функция ПолучитьНомерБезПрефикса(Знач ИсходныйНомер) Экспорт
ПозицияРазделителя = СтрНайти(ИсходныйНомер, "-");
Если ПозицияРазделителя > 0 Тогда
Возврат СокрЛП(Сред(ИсходныйНомер, ПозицияРазделителя + 1));
КонецЕсли;
Возврат ИсходныйНомер;
КонецФункции
Этот подход позволяет избежать технических конфликтов в базе данных, так как платформа видит уникальные номера вида 0012-00001 и 0015-00001, в то время как пользователи в интерфейсе видят привычные «1» и «1».
Выбирая любой из предложенных вариантов автоматизации, мы обязательно должны предупредить заказчика о возможных последствиях. Нумерация документов — это не просто прихоть программиста, это регламентированная область ведения учета.
Проанализируем ключевые риски:
Внедрение раздельной нумерации документов по справочнику — технически реализуемая задача. Однако лучшим решением всегда будет попытка выяснить истинную бизнес-потребность инициатора задачи. Зачастую начальнику цеха или логисту уникальный номер нужен лишь для того, чтобы быстрее ориентироваться в реестрах отгрузок. В этом случае добавление дополнительной колонки с номером договора или использование визуальной маски полностью решит проблему без ущерба для стабильности системы и требований бухгалтерии.