Как сделать нумерацию документов в разрезе контрагентов или договоров в 1С?

Программист 1С v8.3 (Управляемые формы) Управленческий учет
← На главную

В практике автоматизации бизнес-процессов на платформе 1С разработчики часто сталкиваются с нестандартными требованиями со стороны заказчиков или руководителей подразделений. Одно из таких требований — организация независимой нумерации документов для каждого отдельного контрагента или договора. Например, начальник цеха или менеджер по логистике хочет, чтобы отгрузки по Контрагенту А нумеровались по порядку: 1, 2, 3, а по Контрагенту Б параллельно шла своя цепочка номеров: 1, 2, 3.

В этой статье мы подробно проанализируем, почему такое требование конфликтует со стандартными механизмами платформы 1С, рассмотрим риски для бухгалтерского учета и разберем несколько практических вариантов реализации этой задачи с примерами кода.

Архитектурное ограничение платформы 1С и уникальность номеров

Прежде чем переходить к программированию, давайте разберем, как устроена автонумерация в 1С. Платформа проектировалась исходя из принципа, что стандартный реквизит Номер (или Код у справочников) является глобально уникальным идентификатором объекта в рамках определенного периода (года, квартала, месяца или без ограничения периода). Если в метаданных документа включено свойство Контроль уникальности, платформа физически заблокирует запись двух документов с одинаковыми номерами за один период.

Попытка отключить контроль уникальности в свойствах документа — это самый опасный путь. В таком случае стандартный механизм автонумерации перестанет работать корректно, а пользователи запутаются при поиске документов. Поэтому нам необходимо найти решение, которое позволит сохранить системную уникальность номеров на уровне платформы, но при этом предоставит пользователям нужную аналитику.

Решение 1. Использование независимого регистра сведений (Рекомендуемый способ)

Самый надежный и отказоустойчивый метод с точки зрения архитектуры базы данных — это создание отдельного реквизита для хранения «внутреннего» номера и использование независимого регистра сведений в качестве счетчика. Этот способ исключает возникновение дублей при одновременной работе нескольких пользователей (проблема «гонки данных»).

Рассмотрим по шагам, как реализовать этот механизм:

  1. Создадим в документе дополнительный реквизит ВнутреннийНомер (тип: Число или Строка).
  2. Создадим новый независимый регистр сведений НумерацияПоКонтрагентам со следующей структурой:
    • Измерения: Контрагент (тип: СправочникСсылка.Контрагенты), ПериодГод (тип: Число).
    • Ресурсы: ПоследнийНомер (тип: Число).
  3. Напишем обработчик в подписке на событие ПередЗаписью документа для генерации нового номера в транзакции с использованием объектной блокировки.

Посмотрим на пример кода, который решает задачу транзакционного инкремента номера:


Процедура ПередЗаписьюДокументаПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
    
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Проверяем, нужно ли присваивать новый номер
    Если Источник.ЭтоНовый() Или НЕ ЗначениеЗаполнено(Источник.ВнутреннийНомер) Тогда
        
        // Установим блокировку для предотвращения одновременного получения одного номера разными сессиями
        Блокировка = Новый БлокировкаДанных;
        ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НумерацияПоКонтрагентам");
        ЭлементБлокировки.УстановитьЗначение("Контрагент", Источник.Контрагент);
        ЭлементБлокировки.УстановитьЗначение("ПериодГод", Год(Источник.Дата));
        ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
        
        НачатьТранзакцию();
        Попытка
            Блокировка.Заблокировать();
            
            // Запрашиваем последний сохраненный номер по данному контрагенту за текущий год
            Запрос = Новый Запрос;
            Запрос.Текст = 
                "ВЫБРАТЬ
                |   Нумерация.ПоследнийНомер КАК ПоследнийНомер
                |ИЗ
                |   РегистрСведений.НумерацияПоКонтрагентам КАК Нумерация
                |ГДЕ
                |   Нумерация.Контрагент = &Контрагент
                |   И Нумерация.ПериодГод = &ПериодГод";
            
            Запрос.УстановитьПараметр("Контрагент", Источник.Контрагент);
            Запрос.УстановитьПараметр("ПериодГод", Год(Источник.Дата));
            
            Выборка = Запрос.Выполнить().Выбрать();
            
            НовыйНомер = 1;
            Если Выборка.Следующий() Тогда
                НовыйНомер = Выборка.ПоследнийНомер + 1;
            КонецЕсли;
            
            // Обновляем значение счетчика в регистре сведений
            МенеджерЗаписи = РегистрыСведений.НумерацияПоКонтрагентам.СоздатьМенеджерЗаписи();
            МенеджерЗаписи.Контрагент = Источник.Контрагент;
            МенеджерЗаписи.ПериодГод = Год(Источник.Дата);
            МенеджерЗаписи.ПоследнийНомер = НовыйНомер;
            МенеджерЗаписи.Записать(Истина);
            
            // Присваиваем вычисленный номер в документ
            Источник.ВнутреннийНомер = НовыйНомер;
            
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            ВызватьИсключение "Не удалось сформировать уникальный номер по причине: " + ОписаниеОшибки();
        КонецПопытки;
        
    КонецЕсли;
    
КонецПроцедуры

Этот способ позволяет сохранить типовую структуру метаданных конфигурации нетронутой. Стандартное поле Номер продолжит заполняться уникальными значениями платформы, а в печатных формах для начальника цеха мы сможем выводить наш красивый ВнутреннийНомер.

Решение 2. Визуальная маска префикса (Префикс-трансформер)

Рассмотрим ситуацию, когда стандартный номер все же должен быть уникальным, но пользователи категорически отказываются видеть префиксы в интерфейсе программы. Мы можем использовать стандартный механизм префиксации 1С, но "маскировать" его при показе пользователю.

Разберем этот алгоритм по шагам:

  1. При записи документа формируем префикс на основе кода контрагента. Например, если код контрагента в справочнике «0012», то номер документа запишется как 0012-00001.
  2. Для отображения в формах списков и на формах документов создаем вычисляемое текстовое поле.
  3. В модуле формы (или при выводе списка) пишем простую функцию, которая отсекает левую часть номера до дефиса.

Посмотрим на пример функции очистки номера:


Функция ПолучитьНомерБезПрефикса(Знач ИсходныйНомер) Экспорт
    
    ПозицияРазделителя = СтрНайти(ИсходныйНомер, "-");
    Если ПозицияРазделителя > 0 Тогда
        Возврат СокрЛП(Сред(ИсходныйНомер, ПозицияРазделителя + 1));
    КонецЕсли;
    
    Возврат ИсходныйНомер;
    
КонецФункции

Этот подход позволяет избежать технических конфликтов в базе данных, так как платформа видит уникальные номера вида 0012-00001 и 0015-00001, в то время как пользователи в интерфейсе видят привычные «1» и «1».

Важные методологические и законодательные риски

Выбирая любой из предложенных вариантов автоматизации, мы обязательно должны предупредить заказчика о возможных последствиях. Нумерация документов — это не просто прихоть программиста, это регламентированная область ведения учета.

Проанализируем ключевые риски:

Выводы и рекомендации

Внедрение раздельной нумерации документов по справочнику — технически реализуемая задача. Однако лучшим решением всегда будет попытка выяснить истинную бизнес-потребность инициатора задачи. Зачастую начальнику цеха или логисту уникальный номер нужен лишь для того, чтобы быстрее ориентироваться в реестрах отгрузок. В этом случае добавление дополнительной колонки с номером договора или использование визуальной маски полностью решит проблему без ущерба для стабильности системы и требований бухгалтерии.

← На главную