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