Почему не сохраняются и не выводятся остатки товаров в табличной части при проведении документа

Программист 1С v8.3 (Управляемые формы) Управленческий учет
← На главную

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача: отобразить актуальные остатки товаров непосредственно в табличной части документа (например, в «Реализации товаров» или «Расходной накладной»). Однако начинающие разработчики часто сталкиваются с тем, что при нажатии кнопки «Провести» данные в колонке появляются, а при использовании команды «Провести и закрыть» — исчезают или вовсе не записываются в базу. Разберем подробно, почему так происходит, и как реализовать этот механизм правильно.

Разбор основной ошибки: логика работы процедуры ОбработкаПроведения

Проанализируем ситуацию, описанную в теме. Автор пытается рассчитать остатки и записать их в строки табличной части (Строка.Остаток = Результат.КоличествоОстаток;) прямо внутри процедуры ОбработкаПроведения модуля объекта. Это является фундаментальной архитектурной ошибкой по нескольким причинам:

  1. Жизненный цикл документа: В 1С процесс проведения всегда следует после записи документа. Когда пользователь нажимает «Провести», система сначала выполняет транзакцию записи (сохраняет реквизиты и ТЧ в таблицы базы данных), а затем открывает транзакцию проведения. Изменения, внесенные в ЭтотОбъект (в табличные части или реквизиты) внутри ОбработкаПроведения, физически не попадают в базу данных, так как этап записи данных самого документа уже завершен.
  2. Назначение процедуры: Основная и единственная задача ОбработкаПроведения — создание движений по регистрам накопления, бухгалтерии или сведений. Попытка изменить состояние самого объекта в этот момент нарушает логику работы платформы.
  3. Транзакционные блокировки: Если попытаться принудительно вызвать ЭтотОбъект.Записать() внутри проведения, возникнет рекурсия или ошибка блокировки, так как объект уже находится в состоянии записи.

Почему «Провести» работает, а «Провести и закрыть» — нет?

Разберем поведение интерфейса. Рассмотрим ситуацию подробнее:

Когда вы нажимаете кнопку «Провести», форма документа остается открытой. Код в модуле объекта выполняется, данные в оперативной памяти (в коллекции ЭтотОбъект.Перечень) обновляются. Поскольку форма связана с этим объектом, она отображает измененные значения из памяти. Пользователь видит цифры и думает, что все хорошо.

Однако при нажатии «Провести и закрыть» происходит следующее:

  1. Документ записывается (без остатков).
  2. Выполняется ОбработкаПроведения (остатки рассчитываются в памяти, но не сохраняются в БД).
  3. Форма закрывается и уничтожается из оперативной памяти сервера и клиента.
  4. При следующем открытии документа система считывает данные из базы данных. А так как в базе в колонке «Остаток» ничего не было сохранено, пользователь видит пустые поля.

Как правильно организовать хранение и вывод остатков

Существует два принципиально разных подхода, выбор которых зависит от бизнес-требований: нужно ли нам просто видеть остаток в момент работы с документом или мы хотим зафиксировать («заморозить») остаток на момент отгрузки в истории документа.

Вариант 1. Динамический вывод остатков (без сохранения в БД)

Если остатки нужны пользователю только для справки, их не стоит добавлять как реквизиты табличной части в конфигураторе. Это лишний объем данных. Правильнее создать Реквизит формы (не объекта!) в табличной части и заполнять его динамически.

Рассмотрим по шагам, как это сделать:

Шаг 1. В редакторе формы добавьте в колонки табличной части новый реквизит с типом Число. Убедитесь, что он не связан с данными самого документа.

Шаг 2. Используйте событие формы ПриЧтенииНаСервере или ПриОткрытии для первичного заполнения.

Шаг 3. Оптимизируйте запрос. Никогда не делайте запрос в цикле! Проанализируем правильный подход:


&НаСервере
Процедура ЗаполнитьОстаткиНаФорме()
    // 1. Собираем массив товаров из ТЧ, чтобы сделать ОДИН запрос
    МассивТоваров = Объект.Перечень.ВыгрузитьКолонку("Товар");
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ОстаткиТоваровОстатки.Товар КАК Товар,
        |	ОстаткиТоваровОстатки.КоличествоОстаток КАК КоличествоОстаток
        |ИЗ
        |	РегистрНакопления.ОстаткиТоваров.Остатки( , Товар В (&МассивТоваров) И Склад = &Склад) КАК ОстаткиТоваровОстатки";
    
    Запрос.УстановитьПараметр("МассивТоваров", МассивТоваров);
    Запрос.УстановитьПараметр("Склад", Объект.Склад);
    
    РезультатЗапроса = Запрос.Выполнить().Выгрузить();
    
    // 2. Заполняем колонки на форме
    Для Каждого СтрокаТЧ Из Объект.Перечень Цикл
        НайденнаяСтрока = РезультатЗапроса.Найти(СтрокаТЧ.Товар, "Товар");
        Если НайденнаяСтрока <> Неопределено Тогда
            СтрокаТЧ.ОстатокНаФорме = НайденнаяСтрока.КоличествоОстаток;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Вариант 2. Сохранение остатков в документе (Snapshot)

Если задача стоит именно сохранить остаток в табличной части как реквизит объекта, то код расчета нужно перенести из ОбработкаПроведения в процедуру ПередЗаписью модуля объекта.

Выясним причину, почему это лучше: ПередЗаписью срабатывает до того, как данные будут зафиксированы в таблицах базы данных. Любые изменения ЭтотОбъект в этой процедуре будут автоматически сохранены в базу при записи.

Важный нюанс: при записи нового документа он еще не сделал движений «Расход», поэтому остаток будет «полным». Если вам нужен остаток после проведения, логика усложняется, так как проведение — это финальный этап.

Оптимизация производительности: почему нельзя делать запросы в цикле

Посмотрим на пример из сообщения автора темы. Там используется конструкция Для Каждого Строка Из Перечень Цикл ... Запрос.Выполнить(). Представьте, что в документе 100 строк. Программа 100 раз обратится к базе данных, 100 раз скомпилирует запрос и 100 раз получит результат. Это крайне медленно.

Запомните правило: один запрос на одну процедуру. Всегда выгружайте список товаров в массив, передавайте его в параметр запроса с оператором В (&МассивТоваров), получайте таблицу значений и работайте с ней в памяти.

Проверка остатков при проведении

Автор темы также реализовал проверку на нехватку товара. Проанализируем ситуацию: в 1С правильной практикой считается запрет проведения, если товара недостаточно. Но делать это нужно грамотно:

  1. Установите свойство движений Движения.ОстаткиТоваров.Записывать = Истина;.
  2. Запишите пустые наборы или актуальные движения.
  3. Выполните запрос к виртуальной таблице Остатки с фильтром по текущему складу и товарам документа.
  4. Если в результате запроса есть отрицательные числа в поле КоличествоОстаток, выводите сообщение через СообщениеПользователю и устанавливайте Отказ = Истина.

Пример корректного сообщения об ошибке:


Для Каждого ТекСтрока Из РезультатКонтроля Цикл
    Сообщение = Новый СообщениеПользователю;
    Сообщение.Текст = "Недостаточно товара " + ТекСтрока.Товар + ". Требуется: " + ТекСтрока.Нужно + ", в наличии: " + ТекСтрока.Есть;
    Сообщение.Поле = "Перечень[" + ТекСтрока.НомерСтроки + "].Количество";
    Сообщение.УстановитьДанные(ЭтотОбъект);
    Сообщение.Сообщить();
    Отказ = Истина;
КонецЦикла;

Использование свойства Поле позволит пользователю кликнуть на сообщение об ошибке, и система автоматически переведет курсор в нужную строку и ячейку табличной части, где возникла проблема с остатком.

Итоги

Разберем кратко основные выводы из обсуждения:

  1. Не меняйте данные объекта в ОбработкеПроведения — они не сохранятся в базе.
  2. Для визуального контроля используйте реквизиты формы и заполняйте их ПриЧтенииНаСервере.
  3. Для записи данных в БД используйте процедуру ПередЗаписью.
  4. Избегайте запросов в цикле — это «убивает» производительность системы.
  5. Используйте СообщениеПользователю с привязкой к конкретному полю для повышения удобства интерфейса.
← На главную