Как правильно заполнить данные в подчиненном регистре, если они отсутствуют в реквизитах документа?

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

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

Рассмотрим проблему на конкретном примере. У нас есть документ "Рейс" и подчиненный ему регистр сведений "упПеревозчикПоРейсу". На форме документа есть поле для выбора перевозчика, но в самом объекте документа "Рейс" реквизита "Перевозчик" нет. Наша цель — при сохранении документа записать выбранного перевозчика в регистр.

Почему прямая запись в подчиненный регистр — плохая идея?

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


// Пример НЕПРАВИЛЬНОГО кода
// создали набор
НаборЗаписей = РегистрыСведений.упПеревозчикПоРейсу.СоздатьНаборЗаписей();
// установили в нем отбор по регистратору
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокументРейс);
НаборЗаписей.Отбор.Регистратор.Использование = Истина;
// прочитали то, что есть в базе
НаборЗаписей.Прочитать();
// добавили новую запись
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Регистратор = СсылкаНаДокументРейс;
НоваяЗапись.Перевозчик = ВыбранныйПеревозчик;
// записали общий набор обратно
НаборЗаписей.Записать();

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

  1. Потеря данных при перепроведении. Стандартный механизм проведения документа сначала полностью очищает все записи в подчиненных регистрах, связанные с этим документом-регистратором, а затем формирует новый набор записей (движений). Любые данные, записанные напрямую, будут безвозвратно удалены при первом же перепроведении документа.
  2. Нарушение транзакционности. Запись документа и запись в регистр в данном случае — это две разные, не связанные транзакции. Если при записи документа произойдет ошибка и сработает отмена транзакции (Отказ = Истина), то сам документ не запишется, а вот запись в регистре, сделанная ранее, останется. В базе появятся "мусорные" данные — запись в регистре, ссылающаяся на несуществующий или непроведенный документ.
  3. Рассинхронизация данных. При отмене проведения документа платформа автоматически удаляет его движения. Записи, сделанные вручную, останутся в базе, что опять же приведет к накоплению некорректных данных.

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

Правильный механизм: работа через движения документа

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

Для этой цели существует специальное свойство любого объекта — ДополнительныеСвойства. Это хранилище типа Структура, которое "живет" только в оперативной памяти и идеально подходит для передачи произвольных данных между модулями формы и объекта.

Разберем решение по шагам.

Шаг 1: Передача данных с формы в модуль объекта

Сначала нам нужно в модуле формы документа перехватить событие записи и поместить наши данные в ДополнительныеСвойства. Для этого идеально подходит обработчик ПередЗаписью.

Предположим, на форме есть реквизит ПеревозчикНаФорме, куда пользователь выбирает значение.

В модуле формы документа добавляем следующий код:


&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    
    // Создаем структуру для передачи данных
    ПараметрыДляПроведения = Новый Структура;
    
    // Вставляем в нее нашего перевозчика с формы
    ПараметрыДляПроведения.Вставить("Перевозчик", Объект.ПеревозчикНаФорме);
    
    // Помещаем структуру в дополнительные свойства объекта
    // Важно: эта строка будет работать только на сервере
    УстановитьДополнительныеСвойстваНаСервере(ПараметрыДляПроведения);
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура УстановитьДополнительныеСвойстваНаСервере(ПараметрыДляПроведения)
    
    // Присваиваем структуру свойству объекта
    Объект.ДополнительныеСвойства.Вставить("ПараметрыДляПроведения", ПараметрыДляПроведения);

КонецПроцедуры

Теперь, перед тем как объект документа будет записан, в его временное свойство ДополнительныеСвойства будет помещена структура с нужным нам значением перевозчика.

Шаг 2: Формирование движений в модуле объекта

Далее, в модуле объекта документа, в процедуре ОбработкаПроведения, мы можем получить доступ к этим данным и на их основе сформировать правильное движение.


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // ... здесь может быть другой код проведения ...
    
    // Указываем, что мы будем делать записи в этот регистр
    Движения.упПеревозчикПоРейсу.Записывать = Истина;
    
    // Проверяем, были ли переданы наши данные с формы
    Если ЭтотОбъект.ДополнительныеСвойства.Свойство("ПараметрыДляПроведения") Тогда
        
        // Получаем нашу структуру
        ПараметрыДляПроведения = ЭтотОбъект.ДополнительныеСвойства.ПараметрыДляПроведения;
        
        // Проверяем, есть ли в ней нужный ключ
        Если ПараметрыДляПроведения.Свойство("Перевозчик") Тогда
        
            // Получаем значение перевозчика
            ВыбранныйПеревозчик = ПараметрыДляПроведения.Перевозчик;
            
            // Если перевозчик указан, делаем запись в регистр
            Если ЗначениеЗаполнено(ВыбранныйПеревозчик) Тогда
                
                // Добавляем новое движение (новую запись в регистр)
                Движение = Движения.упПеревозчикПоРейсу.Добавить();
                
                // Заполняем измерения и ресурсы
                Движение.Период = Дата; // Период записи
                Движение.Регистратор = Ссылка; // Ссылка на наш документ "Рейс"
                Движение.Перевозчик = ВыбранныйПеревозчик; // Наше значение с формы
                // ... заполняем другие поля при необходимости ...
                
            КонецЕсли;
        
        КонецЕсли;
        
    КонецЕсли;
    
    // ... здесь может быть другой код проведения ...

КонецПроцедуры

Таким образом, мы построили надежный и методологически верный механизм. Данные с формы передаются в объект через ДополнительныеСвойства, а запись в подчиненный регистр происходит в рамках общей транзакции проведения документа. Платформа сама проконтролирует удаление этих записей при отмене проведения и их корректное обновление при перепроведении.

Размышления об архитектуре: проблема "фиктивных" данных

В ходе обсуждения на форуме была затронута еще одна важная тема — использование "фиктивных" элементов справочников. Автор упоминал, что для предварительных расчетов использует "фиктивного перевозчика". Такой подход, хотя и кажется простым решением "здесь и сейчас", часто является признаком архитектурной недоработки и может привести к проблемам в будущем.

Как можно избежать "фикций"?

  1. Использовать пустые значения. Вместо фиктивного элемента использовать Справочники.Перевозчики.ПустаяСсылка(). Логику расчетов нужно доработать так, чтобы она корректно обрабатывала ситуацию, когда перевозчик еще не назначен (например, использовала средний или плановый тариф).
  2. Ввести статусную модель. У документа "Рейс" могут быть статусы: "Планируется", "Назначен перевозчик", "Выполняется". Расчет плановых расходов может быть привязан к статусу "Планируется", а когда перевозчик будет известен и статус изменится, система сможет произвести перерасчет по его реальным тарифам.
  3. Разделить плановые и фактические данные. Можно использовать разные регистры или разные ресурсы в одном регистре для хранения плановых показателей (например, "ПлановыйТариф") и фактических ("ФактическийПеревозчик", "ФактическийТариф"). Это позволит хранить плановую информацию без необходимости привязки к несуществующему перевозчику.

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

← На главную