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