Как лучше создать, изменить и удалить подчиненный документ в 1С?

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

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

Представим, что у нас есть основной документ, и к нему требуется создать один или несколько связанных документов, которые не могут существовать без него и должны автоматически реагировать на его жизненный цикл (создание, запись, проведение, пометка на удаление). Типичный пример из типовых конфигураций — СчетФактураВыданный или СчетФактураПолученный, которые жестко привязаны к документу-основанию (например, РеализацияТоваровУслуг или ПоступлениеТоваровУслуг). Эти документы не имеют смысла без своего основания и часто должны синхронизироваться с ним.

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

Подход 1: Использование подписок на события

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

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

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

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

      Пример из практики, описанный на форуме, где документ ОПЗС (Оперативный план закупок и снабжения) в УПП жестко связан с тремя документами: ТН (Товарная накладная), КорректировкаСерийИХарактеристик и ПеремещениеТоваров. При проведении ОПЗС, если подчиненные документы отсутствуют, они создаются и проводятся. Если они уже существуют, то перезаписываются. Этот подход значительно сократил количество ручных ошибок и улучшил консистентность данных.

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

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

      Однако, при работе с пометкой на удаление, само по себе событие ПередУдалением объекта не вызывается, так как это не физическое удаление, а изменение реквизита ПометкаУдаления. Чтобы отреагировать на пометку на удаление, необходимо проверять состояние реквизита ПометкаУдаления в подписке на событие ПриЗаписи или ПослеЗаписи.

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

Подход 2: Создание при записи или после проведения основного документа (прямое кодирование)

Создание подчиненного документа может быть реализовано непосредственно в модуле основного документа в обработчиках ПриЗаписи или ОбработкаПроведения (или ПослеПроведения для управляемых форм).

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

Подход 3: Использование регистров сведений

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

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

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

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

Подход 4: Механизм связанных документов (реквизит "ДокументОснование")

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

  1. Типы связей: Связи могут быть:
    • Жесткими: Исключают создание подчиненного без основного. Мы можем реализовать это, например, путем проверки заполнения реквизита "Основание" при записи подчиненного документа.
    • Мягкими: Позволяют создавать документы отдельно, но связывать их позже, заполняя реквизит "Основание" вручную или через обработки.
  2. Автозаполнение: Мы можем настроить автозаполнение реквизитов подчиненного документа значениями из основного, используя обработчик ОбработкаЗаполнения в модуле объекта подчиненного документа.
    
    // В модуле объекта документа "ВашПодчиненныйДокумент"
    Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
        СтандартнаяОбработка = Ложь; // Отключаем стандартную обработку, если наша логика покрывает все
        Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ВашОсновнойДокумент") Тогда
            ЭтотОбъект.ДокументОснование = ДанныеЗаполнения;
            ЭтотОбъект.Дата = ДанныеЗаполнения.Дата;
            ЭтотОбъект.Организация = ДанныеЗаполнения.Организация;
            // ... заполняем другие реквизиты
        КонецЕсли;
    КонеПроцедура
    
  3. Удаление: Мы можем настроить поведение при удалении основного документа:
    • Запрет удаления, если есть подчиненные документы (платформа может это делать автоматически при наличии ссылок).
    • Каскадное удаление (реализуется через подписки или программно в ПередУдалением основного документа, как описано выше).
  4. Проблемы с отображением связей: Иногда связанные документы могут не отображаться в структуре подчиненности из-за отсутствия реквизита, ссылающегося на документ-основание, или из-за некорректно настроенных критериев отбора СтруктураПодчиненности или СвязанныеДокументы в конфигураторе. Нам следует проверить наличие и правильность этих настроек.
  5. 1С:Документооборот: В 1С:Документооборот существует специальный механизм "Связи документов", который позволяет настраивать различные типы связей (основание, приложение, связанное с, произвольные). Связи могут быть односторонними или двусторонними, единичными или множественными, а их установка может быть обязательной. Это хороший пример развитого механизма связей.

Обсуждение: "Ошибка проектирования" и оправданность жесткой связи

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

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

Ключевой момент — это анализ:

  1. Наличие собственной логики и уникальных реквизитов: Если подчиненный объект имеет свою сложную логику, набор реквизитов, который не вписывается в основной документ, или формирует собственные движения по регистрам, то отдельный документ оправдан.
  2. Потребность в ссылке: Если на подчиненный объект должны ссылаться другие документы или объекты системы, он должен быть отдельным документом.
  3. Разделение учета: Например, для разделения бухгалтерского и управленческого учета, когда основной документ формирует движения по управленческому учету, а подчиненный — по бухгалтерскому.

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

Выбор оптимального решения

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

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

← На главную