В процессе разработки в среде «1С:Предприятие» часто возникает задача автоматического изменения состояния связанных объектов при проведении документов. Например, при проведении складского или финансового документа необходимо установить определенный флаг в карточке номенклатуры, контрагента или серии номенклатуры.
Начинающие разработчики часто совершают архитектурные ошибки при реализации этого механизма. В этой статье мы подробно разберем, почему нельзя использовать события формы для этой задачи, как правильно написать код в модуле объекта и почему использование Регистров сведений в данном случае является наиболее правильным решением.
Представим сценарий: в документе есть табличная часть, содержащая колонки СерииНоменклатуры и Установить (тип Булево). Если напротив серии установлена галочка, то при проведении документа в карточке соответствующей серии также должен установиться флаг ТребуетсяДооформление в значение Истина.
Рассмотрим первоначальный код, предложенный автором темы на форуме:
Процедура ПослеЗаписи
Серии = Справочники.СерииНоменклатуры.ПустаяСсылка;
НовСтр = ЭтотОбъект.ТЧ.Добавить;
Если НовСтр.Установить = Истина Тогда
Упс = Серии.ПолучитьОбъект;
Упс.ТребуетсяДооформление = Истина;
Упс.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецПроцедуры;
Проанализируем этот фрагмент кода и выделим критические ошибки, которые сделают программу неработоспособной или крайне нестабильной:
Записать() для объектов типа Справочник не принимает параметры режима записи документов (такие как РежимЗаписиДокумента.Запись). Справочники записываются простым вызовом метода без параметров.ПослеЗаписи срабатывает тогда, когда транзакция записи документа уже завершена и зафиксирована в базе данных. Если в процессе записи справочника возникнет ошибка (например, из-за блокировки другим пользователем), документ останется проведенным, а изменения в справочнике не сохранятся. Данные станут рассинхронизированными.Выясним, как решить эту задачу правильно, используя штатные механизмы платформы «1С:Предприятие 8.3».
---
Если бизнес-логика вашей конфигурации жестко требует хранения флага непосредственно внутри реквизитов справочника, то всю работу нужно перенести в Модуль объекта документа. Для этого используются предопределенные обработчики событий ОбработкаПроведения и ОбработкаУдаленияПроведения.
Рассмотрим по шагам, как правильно реализовать этот алгоритм, избежав взаимных блокировок и коллизий данных.
Откроем модуль объекта документа и добавим обработчик проведения. Перед изменением элементов справочника крайне важно наложить на них управляемую блокировку. Это исключит ситуацию, когда два пользователя одновременно проводят разные документы с одинаковыми сериями номенклатуры и вызывают системный сбой базы данных (Deadlock).
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Шаг 1.1. Создаем управляемую блокировку для изменяемых элементов справочника
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.СерииНоменклатуры");
ЭлементБлокировки.ИсточникДанных = ТабличнаяЧастьТовары; // Имя вашей ТЧ
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Ссылка", "СерияНоменклатуры");
Блокировка.Заблокировать();
// Шаг 1.2. Обходим строки табличной части документа
Для Каждого СтрокаТЧ Из ТабличнаяЧастьТовары Цикл
// Проверяем, установлена ли галочка в строке документа
Если СтрокаТЧ.Установить Тогда
// Получаем объект серии для изменения
СерияСсылка = СтрокаТЧ.СерияНоменклатуры;
Если ЗначениеЗаполнено(СерияСсылка) Тогда
СерияОбъект = СерияСсылка.ПолучитьОбъект();
// Оптимизация производительности: записываем только если значение реально изменилось
Если СерияОбъект.ТребуетсяДооформление <> Истина Тогда
СерияОбъект.ТребуетсяДооформление = Истина;
// Чтобы при отмене проведения знать, какой именно документ установил флаг,
// можно использовать служебный реквизит (например, Комментарий)
СерияОбъект.Комментарий = "Установлено документом " + Строка(Ссылка);
Попытка
СерияОбъект.Записать();
Исключение
// Если запись не удалась (например, объект заблокирован), прерываем проведение
ЗаписатьЛогСобытия("Ошибка записи серии: " + ОписаниеОшибки());
Отказ = Истина;
Возврат;
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если мы просто снимем галочку при отмене проведения, то можем нарушить логику работы системы. Что произойдет, если эта же серия номенклатуры выбрана в другом, также проведенном документе? Если мы безусловно снимем флаг, то нарушим актуальность данных для второго документа.
Чтобы обойти эту проблему, при отмене проведения необходимо выполнять запрос к базе данных и проверять: нет ли других проведенных документов, которые также требуют установки этой галочки для данной серии.
Рассмотрим пример кода в обработчике отмены проведения:
Процедура ОбработкаУдаленияПроведения(Отказ)
Для Каждого СтрокаТЧ Из ТабличнаяЧастьТовары Цикл
Если СтрокаТЧ.Установить И ЗначениеЗаполнено(СтрокаТЧ.Set) Тогда
// Запрос проверяет, есть ли ЕЩЕ проведенные документы с этой серией, где стоит галочка
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ДокТЧ.Ссылка КАК Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ДокТЧ
|ГДЕ
| ДокТЧ.СерияНоменклатуры = &Серия
| И ДокТЧ.Установить = ИСТИНА
| И ДокТЧ.Ссылка.Проведен = ИСТИНА
| И ДокТЧ.Ссылка <> &ТекущийДокумент";
Запрос.УстановитьПараметр("Серия", СтрокаТЧ.СерияНоменклатуры);
Запрос.УстановитьПараметр("ТекущийДокумент", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
// Если других проведенных документов с такой настройкой нет, снимаем флаг в справочнике
Если РезультатЗапроса.Пустой() Тогда
СерияОбъект = СтрокаТЧ.СерияНоменклатуры.ПолучитьОбъект();
Если СерияОбъект <> Неопределено И СерияОбъект.ТребуетсяДооформление Тогда
СерияОбъект.ТребуетсяДооформление = Ложь;
СерияОбъект.Комментарий = "";
СерияОбъект.Записать();
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Проанализировав этот вариант, мы видим, что он полностью рабочий, однако требует написания большого объема сложного кода, выполнения тяжелых запросов при отмене проведения и несет риски блокировок базы данных из-за постоянной перезаписи элементов справочника.
---
Как справедливо заметили опытные разработчики в сообщении №5 обсуждаемой темы, попытка использовать реквизиты справочника для фиксации оперативных состояний, зависящих от документов — это архитектурный антипаттерн.
Для хранения оперативных изменяющихся состояний объектов в системе 1С предназначен специальный объект метаданных — Регистр сведений. Давайте разберем по шагам, как красиво и надежно решить эту задачу с помощью подчиненного регистра сведений.
Создадим в конфигураторе новый регистр сведений со следующими параметрами:
СтатусыСерийНоменклатурыПодчинение регистратору (это критически важно!)Серия (тип СправочникСсылка.СерииНоменклатуры)ТребуетсяДооформление (тип Булево)В свойствах вашего документа на вкладке «Движения» укажите созданный регистр сведений СтатусыСерийНоменклатуры в качестве разрешенного регистра для проведения. Теперь напишем код в модуле объекта:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Включаем запись движений по нашему регистру
Движения.СтатусыСерийНоменклатуры.Записывать = Истина;
Для Каждого СтрокаТЧ Из ТабличнаяЧастьТовары Цикл
Если СтрокаТЧ.Установить Тогда
// Добавляем запись в движения документа
Движение = Движения.СтатусыСерийНоменклатуры.Добавить();
Движение.Период = Дата;
Движение.Регистратор = Ссылка;
Движение.Серия = СтрокаТЧ.СерияНоменклатуры;
Движение.ТребуетсяДооформление = Истина;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Внимание: Обратите внимание на то, что для отмены проведения вам вообще не нужно писать код! Платформа «1С:Предприятие» при отмене проведения документа автоматически и бесследно очистит все его записи (движения) в регистре сведений СтатусыСерийНоменклатуры. Это исключает возникновение коллизий и «забытых» галочек.
Чтобы пользователь видел эту галочку на форме серии номенклатуры, мы не будем хранить ее в самом справочнике. Мы будем динамически считывать ее состояние из регистра сведений при открытии формы.
В модуле формы элемента справочника СерииНоменклатуры напишем следующий код:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Считываем актуальное состояние флага из регистра сведений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(СтатусыСерий.ТребуетсяДооформление, ЛОЖЬ) КАК ТребуетсяДооформление
|ИЗ
| РегистрСведений.СтатусыСерийНоменклатуры КАК СтатусыСерий
|ГДЕ
| СтатусыСерий.Серия = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
УстановитьВыборку = Запрос.Выполнить().Выбрать();
Если УстановитьВыборку.Следующий() Тогда
// Заполняем реквизит формы (создайте реквизит формы "ТребуетсяДооформление" с типом Булево)
ЭтотОбъект.ТребуетсяДооформление = УстановитьВыборку.ТребуетсяДооформление;
Иначе
ЭтотОбъект.ТребуетсяДооформление = Ложь;
КонецЕсли;
КонецПроцедуры
Таким образом, мы получили максимально производительное, отказоустойчивое решение, соответствующее стандартам разработки фирмы «1С». Данные всегда консистентны, транзакции не блокируются, а отмена проведения работает автоматически на уровне платформы.