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

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

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

Анализ проблемы: транзакционность и этапы записи документа

Проанализируем типичную ошибку. Разработчик пытается создать подчиненный документ в процедуре ОбработкаПроведения модуля объекта. Логика кажется верной: документ проводится, создаются связанные сущности, и ссылки на них записываются в ЭтотОбъект. Однако на этом этапе физическая запись основной таблицы документа в базу данных уже завершена. Транзакция записи открыта, но данные самого объекта (его реквизитов) уже «улетели» в СУБД. Любое изменение реквизитов внутри ОбработкаПроведения меняет состояние объекта в оперативной памяти, но не инициирует повторную запись этих изменений в таблицу базы данных.

Именно поэтому ссылка визуально появляется в форме только после второго проведения — при первом проходе она записывается в созданный подчиненный документ, но сам главный документ «не знает», что ему нужно еще раз обновить свои поля. Выясним причину, почему нельзя просто вызвать метод Записать() внутри проведения: это приведет к бесконечной рекурсии и критической ошибке платформы.

Решение №1: Использование обработчика ПередЗаписью

Рассмотрим наиболее правильный и надежный способ решения задачи — перенос логики создания подчиненных объектов в обработчик ПередЗаписью модуля объекта. Этот метод вызывается до того, как данные будут зафиксированы в таблицах базы данных, что позволяет нам модифицировать реквизиты «на лету».

Разберем по шагам алгоритм реализации в процедуре ПередЗаписью:

  1. Проверяем режим записи. Нам нужно, чтобы логика срабатывала только при проведении, а не при обычном сохранении черновика.
  2. Проверяем, не созданы ли подчиненные документы ранее, чтобы избежать дублирования.
  3. Создаем программно объекты подчиненных документов.
  4. Записываем их и получаем ссылки.
  5. Присваиваем полученные ссылки реквизитам основного документа.

Посмотрим на пример реализации такого кода:


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

Решение №2: Использование модуля менеджера для выноса логики

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

Рассмотрим пример функции в модуле менеджера, которая вызывается из основного документа:


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

В этом случае в основном документе в процедуре ПередЗаписью мы передаем данные в менеджер, получаем назад готовую ссылку и записываем её в реквизит. Это позволяет избежать нагромождения кода в модуле объекта.

Важные архитектурные нюансы

При реализации автоматического создания документов важно помнить о нескольких критических моментах:

  1. Проблема «грязного чтения»: При создании подчиненного документа на этапе ПередЗаписью, ссылка на «Главный» документ (ЭтотОбъект.Ссылка) может быть еще невалидной (если документ новый и еще ни разу не записывался). В таких случаях сначала нужно убедиться, что основной объект получил ссылку в базе данных, используя метод УстановитьСсылкуНового().
  2. Риски дублирования: Если пользователь решит перепровести документ, ваш код не должен создавать второй, третий и последующие экземпляры подчиненных документов. Всегда проверяйте наличие связи перед созданием.
  3. Отмена проведения: Не забудьте продумать логику удаления или отмены проведения подчиненных документов при отмене проведения главного. Обычно это реализуется в процедуре ОбработкаУдаленияПроведения.
  4. Использование Критериев отбора: Важный совет: В архитектуре 1С считается «хорошим тоном» не хранить ссылку на подчиненный документ в главном, а наоборот — хранить ссылку на «Главный» в подчиненном (реквизит ДокументОснование). Чтобы быстро находить все связанные документы, используйте объект метаданных КритерииОтбора. Это избавит вас от необходимости перезаписывать основной документ и решит проблему транзакционности.

Обновление интерфейса

Если вы все же решили записывать ссылку в реквизит, вы можете заметить, что в открытой форме документа поле остается пустым после нажатия «Провести и закрыть» (если форма не закрылась). Это связано с тем, что форма не всегда автоматически перечитывает данные из базы после завершения транзакции проведения. Чтобы решить эту проблему на управляемых формах, используйте метод ОповеститьОбИзменении(Ссылка) или обновляйте данные формы в обработчике ПослеЗаписиНаСервере.

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

← На главную