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