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