В процессе разработки в среде «1С:Предприятие» перед программистами часто встает задача автоматической очистки табличных частей документов перед их непосредственной записью или проведением. Типичный пример — необходимость удаления строк, в которых количество или сумма равны нулю. Пользователи могут случайно оставлять такие пустые строки, и, чтобы не засорять базу данных лишней информацией, систему обучают автоматической очистке.
В этой статье мы подробно разберем, как правильно перенести интерактивное удаление строк с кнопки формы на стандартное событие записи документа ПередЗаписью, проанализируем разницу в архитектурных подходах, рассмотрим эффективные программные алгоритмы удаления строк и выясним, почему иногда код «отказывается» работать, а платформа 1С может вести себя непредсказуемо.
Представим ситуацию: на управляемой форме документа создана кнопка «Удалить пустые строки», которая успешно справляется со своей задачей на клиенте. Однако программисту необходимо автоматизировать этот процесс, чтобы очистка происходила незаметно для пользователя каждый раз при нажатии на стандартную кнопку «Провести и закрыть» или просто «Записать». Наша цель — встроить этот функционал в транзакцию записи документа.
Рассмотрим по шагам, как решить эту задачу правильно с точки зрения стандартов разработки «1С».
Первый вопрос, на который нам необходимо ответить: где именно должен располагаться код очистки табличной части? У нас есть два варианта размещения обработчика ПередЗаписью — в модуле управляемой формы или в модуле объекта документа. Проанализируем оба подхода.
Этот обработчик срабатывает только тогда, когда пользователь интерактивно записывает документ, находясь непосредственно в его форме. Если документ будет записываться программно (например, при обмене данными, групповом перепроведении, загрузке из Excel или создании на основании другого документа), код в модуле формы не выполнится. Следовательно, в базе данных все равно останутся пустые строки. Кроме того, обработка больших объемов данных на клиенте или постоянный перенос данных между клиентом и сервером формы снижают общую производительность системы.
Код в модуле объекта выполняется на сервере всегда, вне зависимости от того, как именно происходит запись — интерактивно пользователем или программно из другого алгоритма. Это гарантирует абсолютную целостность данных. Именно здесь и следует размещать логику очистки табличных частей.
Разберем важный нюанс, связанный с контекстом исполнения. В модуле формы мы обращаемся к данным через реквизит формы Объект (например, Объект.ТЧМат). В модуле объекта контекст формы отсутствует «как класс». Переменной Объект там не существует, а обращение к собственным реквизитам и табличным частям происходит напрямую по их именам (или через ключевое слово ЭтотОбъект).
Посмотрим на пример неправильного кода в модуле объекта, который часто пишут начинающие разработчики:
// ОШИБКА: Переменная "Объект" не определена в модуле объекта!
Для Каждого Строка Из Объект.ТЧМат Цикл
// Логика проверки...
КонецЦикла;
Правильное обращение к табличной части в модуле объекта должно выглядеть так:
// Правильно: обращение напрямую к табличной части объекта
Для Каждого Строка Из ТЧМат Цикл
// Логика проверки...
КонецЦикла;
Многие разработчики совершают классическую ошибку, пытаясь удалять строки прямо во время обычного обхода коллекции. Разберем, почему так делать нельзя и какие методы являются наиболее эффективными.
Рассмотрим ошибочный алгоритм прямого обхода:
// ОШИБКА: Прямой обход с удалением приводит к пропуску строк
Для Каждого Строка Из ТЧМат Цикл
Если Строка.Количество = 0 Тогда
ТЧМат.Удалить(Строка);
КонецЕсли;
КонецЦикла;
При удалении строки в таком цикле индексы всех последующих строк в таблице сдвигаются на единицу вверх. Счетчик цикла при этом продолжает увеличиваться. В результате платформа «перешагнет» через одну строку. Если в вашей табличной части будут идти подряд две строки с нулевым количеством, то вторая из них гарантированно останется не удаленной. Это критическая логическая ошибка.
Чтобы избежать этой проблемы, рассмотрим три правильных и эффективных метода решения.
Этот метод заключается в том, что сначала мы находим все строки, соответствующие нашему условию, собираем ссылки на них в массив, и только затем вторым циклом удаляем элементы из табличной части. Именно этот подход пытался реализовать автор темы на форуме:
МассивДляУдаления = Новый Массив;
// Шаг 1. Собираем пустые строки в массив
Для Каждого Строка Из ТЧМат Цикл
Если Строка.Количество = 0 Тогда
МассивДляУдаления.Добавить(Строка);
КонецЕсли;
КонецЦикла;
// Шаг 2. Удаляем собранные строки из ТЧ
Для Каждого Строка Из МассивДляУдаления Цикл
ТЧМат.Удалить(Строка);
КонецЦикла;
Этот способ абсолютно рабочий и безопасный, поскольку индексы элементов в процессе первоначального поиска не меняются. Однако его можно существенно оптимизировать.
Если условием удаления является точное равенство конкретного реквизита какому-то значению (в нашем случае Количество = 0), самым элегантным и быстрым решением будет использование встроенного метода НайтиСтроки(). Платформа 1С оптимизирует этот поиск на низком уровне, что позволяет коду работать значительно быстрее:
ПараметрыОтбора = Новый Структура("Количество", 0);
СтрокиДляУдаления = ТЧМат.НайтиСтроки(ПараметрыОтбора);
Для Каждого ТекущаяСтрока Из СтрокиДляУдаления Цикл
ТЧМат.Удалить(ТекущаяСтрока);
КонецЦикла;
Этот код лаконичен, легко читается и работает максимально быстро на больших объемах данных.
В случаях, когда условия удаления более сложные (например, проверяется диапазон значений, заполненность нескольких реквизитов или выполняются расчеты), метод НайтиСтроки() использовать не удастся. Тогда на помощь приходит обход табличной части с конца к началу по индексам. При таком подходе сдвиг элементов происходит ниже текущего обрабатываемого индекса, что исключает пропуск строк:
Индекс = ТЧМат.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТЧ = ТЧМат[Индекс];
// Произвольное условие любой сложности
Если СтрокаТЧ.Количество = 0 Или СтрокаТЧ.Сумма = 0 Тогда
ТЧМат.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Очень часто очистку табличной части логично выполнять не при каждом промежуточном сохранении черновика документа (нажатии кнопки «Записать»), а исключительно в момент его непосредственного проведения (нажатии «Провести и закрыть»). Для этого в предопределенном обработчике модуля объекта ПередЗаписью предусмотрен параметр РежимЗаписи.
Проанализируем пример кода с проверкой режима проведения документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что документ именно проводится
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
ПараметрыОтбора = Новый Структура("Количество", 0);
СтрокиДляУдаления = ТЧМат.НайтиСтроки(ПараметрыОтбора);
Для Каждого ТекущаяСтрока Из СтрокиДляУдаления Цикл
ТЧМат.Удалить(ТекущаяСтрока);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Таким образом, если пользователь просто записывает документ, пустые строки сохранятся для возможности их дальнейшего редактирования. Но в момент проведения система автоматически уберет весь «мусор» из табличной части.
Существует серьезная скрытая проблема, о которой часто забывают начинающие разработчики. В большинстве документов в шапке присутствуют итоговые реквизиты, такие как СуммаДокумента или Всего. Обычно расчет этих реквизитов привязан к событиям формы (например, к событию ПриИзменении количества или цены в строке табличной части).
Когда вы производите «тихое» удаление пустых строк в модуле объекта на сервере в событии ПередЗаписью, интерактивные обработчики формы не срабатывают. В результате строки с нулевым количеством удаляются, но если у них по какой-то причине была заполнена сумма (например, пользователь ввел сумму вручную, а количество стер), итоговая сумма в шапке документа останется прежней и станет неверной!
Решение проблемы: После программного удаления строк в модуле объекта необходимо принудительно пересчитывать зависимые итоговые показатели шапки документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
// Удаляем строки
ПараметрыОтбора = Новый Структура("Количество", 0);
СтрокиДляУдаления = ТЧМат.НайтиСтроки(ПараметрыОтбора);
Для Каждого Строка Из СтрокиДляУдаления Цикл
ТЧМат.Удалить(Строка);
КонецЦикла;
// КРИТИЧЕСКИ ВАЖНО: Принудительный пересчет итогов шапки
СуммаДокумента = ТЧМат.Итог("Сумма");
КонецЕсли;
КонецПроцедуры
В обсуждении на форуме автор столкнулся с ситуацией, когда написанный им код вообще не реагировал на запись, функция Сообщить() ничего не выводила, а в процессе отладки программа аварийно завершила работу. Разберем причины такого поведения платформы.
Если вы просто напишете процедуру ПередЗаписью в модуле формы вручную, платформа 1С не будет вызывать ее автоматически. Необходимо открыть палитру свойств формы, найти событие ПередЗаписью и привязать к нему созданную процедуру нажатием на кнопку с лупой. Для модуля объекта процедура должна быть предопределенной (иметь строго заданное платформой имя и параметры).
Динамическое обновление (обновление конфигурации базы данных без отключения активных пользователей) — это удобная, но крайне опасная функция. При его частом использовании локальный кэш конфигурации на компьютерах пользователей и разработчиков начинает работать асинхронно с сервером. Платформа физически перестает «видеть» изменения в коде, не реагирует на точки останова при отладке, игнорирует вызовы новых процедур и команд вроде Сообщить().
В худших случаях это приводит к критическому сбою внутренних потоков платформы, и программа аварийно закрывается с ошибкой «Прекращена работа программы 1С:Предприятие». Именно это произошло у автора темы.
Как с этим бороться? Избегайте использования динамического обновления при серьезных изменениях структуры кода и модулей. Если сбой уже произошел, выполните следующие действия:
%userprofile%\AppData\Local\1C\1cv8 и %userprofile%\AppData\Roaming\1C\1cv8).Мы детально изучили процесс удаления строк табличной части документа перед записью. Помните три ключевых правила:
НайтиСтроки() для быстрого и безопасного удаления строк, соответствующих точным условиям отбора.