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