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

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

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

Основной принцип: работа в транзакции

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

Реализация через модуль объекта

Рассмотрим ситуацию на примере автора темы: есть документ Табель, в котором выбирается ссылка на документ КС (контрольная сумма/справка). Нам нужно, чтобы КС следовал за состоянием Табеля. Проанализируем код, который необходимо разместить в модуле объекта Документ.Табель:


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

В данном примере использование параметров РежимЗаписи и РежимПроведения позволяет автоматически транслировать действия пользователя. Если пользователь нажимает "Отмена проведения" в Табеле, в ОбъектКС.Записать() передастся РежимЗаписиДокумента.ОтменаПроведения, и связанный документ также станет непроведенным.

Альтернативный вариант: Подписки на события

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

  1. Создаем новый объект метаданных ПодпискаНаСобытие.
  2. В поле Источник выбираем ДокументОбъект.Табель.
  3. В поле Событие выбираем ПередЗаписью.
  4. В поле Обработчик указываем процедуру в общем модуле (модуль должен иметь флаг "Сервер" и "Вызов сервера" не обязателен).

Код в общем модуле будет выглядеть аналогично, но с учетом того, что первым параметром передается сам Источник:


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

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

Важные технические нюансы (DataExchange и Рекурсия)

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

1. Обработка обмена данными (DataExchange.Load). Рассмотрим ситуацию: вы загружаете данные из другой базы. Если не добавить проверку Если ОбменДанными.Загрузка Тогда Возврат; КонецЕсли;, то при загрузке Табеля система попытается перезаписать КС. Если КС еще не успел загрузиться или уже был изменен, возникнет ошибка или нарушение логики обмена.

2. Предотвращение зацикливания. Представим, что вы решили сделать двустороннюю связь: Табель меняет КС, а КС при изменении должен обновить Табель. Проанализируем последствия: запись первого вызовет запись второго, запись второго снова вызовет запись первого — программа уйдет в бесконечную рекурсию и упадет по стеку. Чтобы этого избежать, используйте ДополнительныеСвойства:


// В ведущем документе
ДокументКС.ДополнительныеСвойства.Вставить("ЭтоСинхронизация", Истина);
ДокументКС.Записать();

// В ведомом документе (КС) в начале ПередЗаписью
Если ДополнительныеСвойства.Свойство("ЭтоСинхронизация") Тогда
    Возврат; // Выходим, чтобы не запускать обратную запись
КонецЕсли;

Проблема "старых данных" в печатных формах

Разберем ситуацию из сообщения №26: пользователь проводит Табель, открывает печатную форму КС, но видит там старые цифры, хотя в форме документа они обновились. Выясним причину:

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

Оперативное и неоперативное проведение

Посмотрим на параметр РежимПроведения. Если ваш основной документ проводится оперативно (текущей датой и временем), то и связанный документ попытается провестись оперативно. Однако, если КС создан вчерашним числом, попытка оперативного проведения выдаст ошибку "Документ не может быть проведен оперативно".

Чтобы избежать этой проблемы, проанализируем бизнес-логику: если вам не важен контроль остатков в момент записи, всегда указывайте для связанного документа неоперативный режим:

ОбъектКС.Записать(РежимЗаписи, РежимПроведенияДокумента.Неоперативный);

Итоги и рекомендации

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

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

← На главную