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