Почему не заполняется табличная часть документа после записи и как правильно работать с остатками в 1С?

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

При разработке расширений для типовых конфигураций, таких как "1С:Управление торговлей 11" (УТ 11), программисты часто сталкиваются с ситуацией, когда доработка корректно функционирует на новом документе, но "ломается" сразу после его записи или проведения. В этой статье мы разберем классический пример такой ошибки: кнопка заполнения товаров по остаткам перестает работать, если документ уже записан. Проанализируем причины, связанные с моментами времени, особенностями архитектуры УТ 11 и стилем кода.

Суть проблемы

Рассмотрим ситуацию: разработчик добавляет кнопку "Заполнить" в документ "Перемещение товаров". Алгоритм должен подобрать товары по определенным критериям (характеристикам) и заполнить ими табличную часть. На новом (еще не записанном) документе все работает идеально. Но стоит пользователю записать документ и нажать кнопку снова — табличная часть очищается или не заполняется вовсе, хотя отладчик может показывать наличие данных.

Здесь кроется совокупность трех распространенных ошибок:

  1. Логическая ошибка при получении остатков (конфликт с движениями самого документа).
  2. Ошибки заполнения специфичных реквизитов УТ 11 (Упаковки vs Количество).
  3. Архитектурная ошибка передачи данных с сервера на клиент.

Разберем каждую из них подробно и сформируем правильное решение.

Причина 1: "Исчезающие" остатки или ловушка ТекущейДаты

Самая частая причина, почему механизм заполнения перестает работать после проведения документа, кроется в запросе к виртуальной таблице остатков.

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


Запрос.УстановитьПараметр("Момент", ТекущаяДата());
...
|ИЗ РегистрНакопления.ТоварыОрганизаций.Остатки(&Момент, ...)

Что происходит при этом:

  1. Когда документ новый, он еще не сделал движений в регистре. Остатки на складе есть, запрос их видит и возвращает.
  2. Когда документ проведен, он списывает товары со склада-отправителя.
  3. Если мы запрашиваем остатки на ТекущаяДата(), система смотрит состояние склада после выполнения всех движений, включая движения нашего текущего документа.
  4. Результат: так как документ сам же и списал товар, остаток на текущий момент равен нулю (или уменьшен). Заполнять нечем.

Как исправить:

Необходимо получать остатки на момент времени документа, но исключая движения самого документа. Для этого в платформе 1С существует объект Граница.

Правильный алгоритм определения момента времени:


Если ЗначениеЗаполнено(Объект.Ссылка) И Объект.Ссылка.Проведен Тогда
    // Если документ проведен, берем момент времени ДО его движений
    ГраницаОстатков = Новый Граница(Объект.Ссылка.МоментВремени(), ВидГраницы.Исключая);
Иначе
    // Если документ новый или не проведен, можно брать текущую дату или дату документа
    ГраницаОстатков = Неопределено; // Или ТекущаяДата(), если нужно на сейчас
КонецЕсли;

Запрос.УстановитьПараметр("Момент", ГраницаОстатков);

Причина 2: Упаковки против Количества в УТ 11

Вторая проблема, специфичная для конфигураций ERP, КА и УТ 11, заключается в разделении понятий "Количество" (в единицах хранения) и "Количество упаковок" (в единицах измерения, выбранных пользователем).

В проблемном коде заполнялось только поле КоличествоУпаковок:


Строка.КоличествоУпаковок = ...;
// Поле Строка.Количество осталось равным 0

Почему это проблема:

В типовых механизмах форм УТ 11 часто стоят отборы или условное оформление, скрывающее строки с нулевым количеством, либо при записи документа происходит валидация, которая считает такую строку некорректной. К тому же, движения по регистрам делаются именно по полю Количество. Если оно равно нулю, движений не будет.

Как исправить:

Необходимо заполнять оба поля. Если упаковки не используются, они равны. Если используются — нужно учитывать коэффициент.


Строка.КоличествоУпаковок = Выборка.КоличествоОстаток; // Допустим, мы нашли остаток в упаковках
// Необходимо получить данные упаковки, если она есть
Если ЗначениеЗаполнено(Строка.Упаковка) Тогда
    // Здесь должна быть логика получения коэффициента упаковки (числитель/знаменатель)
    // В простейшем случае:
    Строка.Количество = Строка.КоличествоУпаковок * КоэффициентУпаковки;
Иначе
    Строка.Количество = Строка.КоличествоУпаковок;
КонецЕсли;

Причина 3: Неэффективная работа с данными (Клиент-Сервер)

В исходном примере использовался "плоский" массив для передачи данных с сервера на клиент и последующего цикла на клиенте. Это считается плохим тоном ("говнокодом") и вызывает ряд проблем:

Как сделать правильно:

Лучшая практика — заполнять табличную часть прямо на сервере, используя метод Загрузить() или ЗаполнитьЗначенияСвойств().

Итоговое правильное решение

Соберем все исправления в единый пример кода. Откажемся от возврата массива на клиент и сделаем всё в одной серверной процедуре.

Код в модуле формы:


&НаКлиенте
Процедура ОбработатьТовары(ВыбранныеХарактеристики)
    
    // Передаем управление на сервер
    ЗаполнитьТоварыНаСервере(ВыбранныеХарактеристики);
    
КонецПроцедуры

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

Резюме

Если ваша табличная часть пуста после записи документа, проверьте следующее:

  1. Используете ли вы Границу с видом Исключая при получении остатков для проведенного документа?
  2. Заполняете ли вы реквизит Количество, а не только КоличествоУпаковок?
  3. Не теряются ли данные при передаче между клиентом и сервером (рекомендуется заполнять ТЧ на сервере)?
← На главную