Как при проведении документа изменить реквизит справочника в 1С?

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

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

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

Анализ исходной задачи и типичные ошибки разработчиков

Представим сценарий: в документе есть табличная часть, содержащая колонки СерииНоменклатуры и Установить (тип Булево). Если напротив серии установлена галочка, то при проведении документа в карточке соответствующей серии также должен установиться флаг ТребуетсяДооформление в значение Истина.

Рассмотрим первоначальный код, предложенный автором темы на форуме:


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

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

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

Выясним, как решить эту задачу правильно, используя штатные механизмы платформы «1С:Предприятие 8.3».

---

Решение №1: Прямое изменение справочника в модуле объекта (Классический вариант)

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

Рассмотрим по шагам, как правильно реализовать этот алгоритм, избежав взаимных блокировок и коллизий данных.

Шаг 1. Реализация проведения в модуле объекта

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


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

Шаг 2. Реализация отмены проведения (Решение проблемы коллизий)

Если мы просто снимем галочку при отмене проведения, то можем нарушить логику работы системы. Что произойдет, если эта же серия номенклатуры выбрана в другом, также проведенном документе? Если мы безусловно снимем флаг, то нарушим актуальность данных для второго документа.

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

Рассмотрим пример кода в обработчике отмены проведения:


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

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

---

Решение №2: Использование регистра сведений (Рекомендуемый стандарт «1С»)

Как справедливо заметили опытные разработчики в сообщении №5 обсуждаемой темы, попытка использовать реквизиты справочника для фиксации оперативных состояний, зависящих от документов — это архитектурный антипаттерн.

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

Шаг 1. Создание Регистра сведений

Создадим в конфигураторе новый регистр сведений со следующими параметрами:

Шаг 2. Настройка проведения в документе

В свойствах вашего документа на вкладке «Движения» укажите созданный регистр сведений СтатусыСерийНоменклатуры в качестве разрешенного регистра для проведения. Теперь напишем код в модуле объекта:


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

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

Шаг 3. Отображение галочки на форме элемента справочника

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

В модуле формы элемента справочника СерииНоменклатуры напишем следующий код:


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

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

← На главную