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

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

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

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

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

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

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

Разбор уязвимости в коде с использованием дополнительных реквизитов

Рассмотрим код, который часто предлагают на форумах в качестве решения (подобный пример приводился в Сообщении 9 исходной темы):


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

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

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

  1. Пользователь переводит документ из статуса «Закупка» в «Производство» и проводит его. Условие в ПередЗаписью срабатывает, флаг взводится, движения списания успешно записываются в регистр.
  2. Спустя некоторое время другой пользователь открывает этот же документ, чтобы просто изменить текстовый комментарий или проверить спецификацию, после чего нажимает стандартную кнопку «Провести и закрыть».
  3. Перед запуском обработчика проведения платформа автоматически очищает все существующие движения этого документа по регистрам.
  4. Вызывается процедура ПередЗаписью. На этот раз и Ссылка.Статус в базе данных, и текущий Статус в записываемом объекте уже равны значению «Производство». Условие перехода статуса не выполняется, и флаг в дополнительные реквизиты не устанавливается.
  5. Вызывается ОбработкаПроведения. Поскольку флага нет, процедура списания СделатьДвиженияСписанияТоваров() игнорируется.
  6. Результат: Документ успешно перепровелся, статус остался «Производство», но все его движения по списанию товаров полностью и безвозвратно исчезли из системы!

Правильная реализация логики проведения в модуле объекта

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

Посмотрим на пример правильного и отказоустойчивого кода в модуле объекта:


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

Этот подход полностью исключает потерю движений при повторном проведении документа и автоматически решает проблему отката статуса: если пользователь вернет статус обратно в значение «Закупка» и проведет документ, система очистит старые движения списания, а новые созданы не будут, так как условие по статусу не выполнится.

Обеспечение «автоматического» списания для пользователя в модуле формы

Теперь вернемся к требованию пользователя: «списание должно происходить автоматически при изменении статуса на форме, а не при нажатии кнопки проведения». Мы можем элегантно реализовать это интерактивно, программно инициировав запись и проведение документа в момент смены статуса.

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

  1. Откроем форму документа и найдем элемент управления (поле ввода или переключатель), связанный с реквизитом Статус.
  2. Создадим для него клиентский обработчик события ПриИзменении.
  3. В обработчике проверим, изменился ли статус на целевой («Производство»), и если да — программно вызовем запись объекта с режимом проведения.

Посмотрим на пример реализации клиентского кода:


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

При таком подходе соблюдаются все стандарты разработки платформы «1С:Предприятие»:

Архитектурные альтернативы (Best Practices)

В крупных тиражных конфигурациях (таких как 1С:ERP или 1С:Комплексная автоматизация) смешивание разнородных бизнес-процессов (закупки и производства) в рамках одного физического документа считается плохой практикой. Проанализируем более гибкие альтернативные подходы:

← На главную