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

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

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

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

Основная идея: вся логика внутри транзакции проведения

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

Если в этой процедуре установить параметр Отказ = Истина, платформа автоматически отменит всю транзакцию, и никакие изменения, включая движения документа, не будут сохранены в базе данных. Наша задача — выполнить проверку именно в этой процедуре, но уже после того, как данные для анализа (движения) стали доступны.

Решение 1: Принудительная запись движений (рекомендуемый метод)

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

Разберем по шагам:

  1. Формируем движения. В процедуре ОбработкаПроведения, как обычно, вы заполняете наборы записей регистров. На этом этапе движения существуют только в памяти.

  2. Принудительно записываем движения. Для каждого набора записей, который нужно проанализировать, вызовите метод Записать(). Важный момент: несмотря на вызов записи, данные еще не зафиксированы окончательно, так как мы находимся внутри транзакции. Однако в режиме управляемых блокировок эти "грязные" данные становятся видимы для запросов внутри этой же транзакции.

    
    Процедура ОбработкаПроведения(Отказ, РежимПроведения)
        
        // 1. Блокируем данные для избежания взаимоблокировок
        // ... код блокировки ...
    
        // 2. Формируем движения документа
        Движения.ОстаткиТоваров.Записывать = Истина;
        Для Каждого Стр Из Товары Цикл
            Движение = Движения.ОстаткиТоваров.Добавить();
            Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
            Движение.Период = Дата;
            Движение.Номенклатура = Стр.Номенклатура;
            Движение.Склад = Склад;
            Движение.Количество = Стр.Количество;
        КонецЦикла;
    
        // 3. Принудительно записываем набор записей
        // Это ключевой шаг. Движения попадают в базу данных в рамках текущей транзакции.
        Движения.ОстаткиТоваров.Записать();
    
        // 4. Выполняем проверку, запрашивая данные из регистра
        // Запрос увидит только что записанные нами движения
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |    ОстаткиТоваровОстатки.Номенклатура,
            |    ОстаткиТоваровОстатки.КоличествоОстаток КАК Остаток
            |ИЗ
            |    РегистрНакопления.ОстаткиТоваров.Остатки(
            |            ,
            |            Номенклатура В (&СписокНоменклатуры) И Склад = &Склад) КАК ОстаткиТоваровОстатки
            |ГДЕ
            |    ОстаткиТоваровОстатки.КоличествоОстаток < 0";
        
        Запрос.УстановитьПараметр("СписокНоменклатуры", Товары.ВыгрузитьКолонку("Номенклатура"));
        Запрос.УстановитьПараметр("Склад", Склад);
        
        РезультатПроверки = Запрос.Выполнить().Выгрузить();
    
        // 5. Принимаем решение об отмене транзакции
        Если РезультатПроверки.Количество() > 0 Тогда
            Отказ = Истина; // Если есть отрицательные остатки, отменяем проведение
            Сообщить("Обнаружены отрицательные остатки по номенклатуре: " + РезультатПроверки[0].Номенклатура);
        КонецЕсли;
    
    КонецПроцедуры
    
  3. Анализируем результат. После записи движений выполняем запрос к регистрам. Этот запрос уже учтет движения текущего документа, и вы сможете получить итоговую картину — например, остатки после списания.

  4. Отменяем транзакцию. Если проверка выявила проблему, устанавливаем Отказ = Истина. Платформа сама позаботится об откате транзакции и удалении всех движений, которые вы записали на шаге 2.

Этот подход является надежным и используется в современных типовых конфигурациях, таких как 1С:ERP, для реализации оперативного контроля остатков.

Решение 2: Использование временных таблиц в запросе

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

Суть метода: смоделировать будущие движения документа во временной таблице и объединить ее с реальными данными регистров в одном запросе.

  1. В процедуре ОбработкаПроведения сначала подготовьте данные о будущих движениях в таблицу значений.

  2. Создайте запрос, в котором с помощью менеджера временных таблиц поместите эту таблицу значений во временную таблицу.

  3. В этом же запросе соедините вашу временную таблицу (с движениями документа) с таблицей остатков (или оборотов) нужного регистра.

  4. Сгруппируйте данные и проанализируйте итоговые значения. Если результат вас не устраивает — установите Отказ = Истина.

Посмотрим на пример контроля остатков этим методом:


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

    // 2. Выполняем проверку одним запросом
    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
    
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    ТаблицаДвижений.Номенклатура КАК Номенклатура,
        |    ТаблицаДвижений.Количество КАК КоличествоРасход
        |ПОМЕСТИТЬ ВТ_ДвиженияДокумента
        |ИЗ
        |    &ТаблицаДвижений КАК ТаблицаДвижений
        |;
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |    ОстаткиИДвижения.Номенклатура,
        |    СУММА(ОстаткиИДвижения.Количество) КАК КонечныйОстаток
        |ИЗ
        |    (ВЫБРАТЬ
        |        ОстаткиТоваровОстатки.Номенклатура КАК Номенклатура,
        |        ОстаткиТоваровОстатки.КоличествоОстаток КАК Количество
        |    ИЗ
        |        РегистрНакопления.ОстаткиТоваров.Остатки(, Склад = &Склад) КАК ОстаткиТоваровОстатки
        |    
        |    ОБЪЕДИНИТЬ ВСЕ
        |    
        |    ВЫБРАТЬ
        |        ВТ_ДвиженияДокумента.Номенклатура,
        |        -ВТ_ДвиженияДокумента.КоличествоРасход
        |    ИЗ
        |        ВТ_ДвиженияДокумента КАК ВТ_ДвиженияДокумента) КАК ОстаткиИДвижения
        |СГРУППИРОВАТЬ ПО
        |    ОстаткиИДвижения.Номенклатура
        |ИМЕЮЩИЕ
        |    СУММА(ОстаткиИДвижения.Количество) < 0";

    Запрос.УстановитьПараметр("ТаблицаДвижений", ТаблицаДвижений);
    Запрос.УстановитьПараметр("Склад", Склад);
    
    РезультатПроверки = Запрос.Выполнить();

    Если Не РезультатПроверки.Пустой() Тогда
        Отказ = Истина;
        Сообщить("Обнаружены отрицательные остатки при проведении документа.");
        Возврат; // Прерываем дальнейшее выполнение
    КонецЕсли;

    // Если проверка прошла, формируем и записываем реальные движения
    Движения.ОстаткиТоваров.Записывать = Истина;
    // ... код формирования движений ...
    
КонецПроцедуры

Решение 3: Программный вызов отчета СКД (рискованный подход)

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

Этот подход не рекомендуется и должен применяться в исключительных случаях, так как он:

Если вы все же решили пойти этим путем, вам потребуется программно получить схему компоновки данных, установить ее параметры, выполнить компоновку макета и вывести результат в таблицу значений для дальнейшего анализа. Если анализ выявит проблему — устанавливаете Отказ = Истина.

Резюме

Таким образом, для решения задачи отмены проведения на основе анализа его же результатов следует придерживаться следующего:

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