Как получить данные из вложенной Таблицы Значений в 1С и записать их в регистр?

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

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

Для начинающих разработчиков обработка подобных конструкций часто становится серьезным препятствием. Типичные симптомы ошибок — запись в регистр только одной (последней) строки, дублирование записей или неверное распределение сумм по номенклатурным позициям. В этой статье мы подробно разберем, почему возникают такие проблемы, проанализируем типичные ошибки в кодовой логике и рассмотрим два оптимальных варианта решения задачи в зависимости от требований к детализации учета.

Давайте вместе разберем эту задачу по шагам, чтобы раз и навсегда научиться правильно работать с вложенными коллекциями в 1С.


Анализ типичных ошибок при обходе вложенных циклов

Для начала давайте внимательно изучим фрагмент кода, который часто пишут разработчики при попытке решить подобную задачу. Рассмотрим логическую структуру, которая приводит к некорректному результату:


Для Каждого ТекСтрокаМатериалы Из Материалы Цикл
    Движение = Движения.Запчасти.Добавить;
    Движение.Организация = Организация;
    Движение.Водитель = Водитель;
    Движение.Номенклатура = ТекСтрокаМатериалы.Номенклатура;
    Движение.Автомобиль = Автомобиль;
    Движение.Количество = ТекСтрокаМатериалы.Количество;
    Движение.Документ = ЭтотОбъект.Ссылка;
    
    Для Каждого СтрокаМатериалов Из ТаблицаМатериалов Цикл
        Для Каждого ТекущСтр Из СтрокаМатериалов.СписанныеПартииБУ Цикл
            СуммаПартий = ТекущСтр.СуммаСписания;
        КонецЦикла;
        движение.Сумма = суммаПартий;
    КонецЦикла;
КонецЦикла;

Проанализируем эту ситуацию и выясним причины, по которым этот код работает некорректно:

  1. Отсутствие связи между коллекциями (Декартово произведение): Обратите внимание на внешний цикл по коллекции Материалы. Внутри него запускается полный обход таблицы ТаблицаМатериалов без какого-либо отбора по номенклатуре. Это означает, что для абсолютно каждого материала из документа система будет полностью перебирать все партии всех остальных материалов.
  2. Неверное позиционирование метода Добавить(): Новая строка движения в регистре создается во внешнем цикле (один раз для материала). Затем во внутренних циклах по партиям свойству Движение.Сумма многократно присваивается значение. Каждая новая итерация просто перезаписывает это свойство. В итоге в регистр записывается только сумма из самой последней строки самой последней партии.
  3. Замена сложения присваиванием: В строке СуммаПартий = ТекущСтр.СуммаСписания; происходит обычное замещение значения переменной. Даже если бы мы хотели накопить общую сумму по партиям, данный код этого не сделал бы. Для накопления требовалось бы написать СуммаПартий = СуммаПартий + ТекущСтр.СуммаСписания;.

Вариант решения 1. Подетальная запись каждой партии (Детализированный учет)

Если перед нами стоит задача отразить в регистре каждую списанную партию отдельно (например, для ведения полноценного партионного учета непосредственно в регистре «Запчасти»), нам необходимо перенести инициализацию и добавление новой записи движения в самый глубокий уровень вложенности. Также критически важно установить жесткую связь между обрабатываемым материалом и строками таблицы партий.

Рассмотрим пример правильной реализации этого подхода:


// Включаем флаг записи набора движений
Движения.Запчасти.Записывать = Истина;

Для Каждого ТекСтрокаМатериалы Из Материалы Цикл
    
    // Шаг 1. Ищем строку в ТаблицеМатериалов, соответствующую текущей номенклатуре
    ПараметрыОтбора = Новый Структура("Номенклатура", ТекСтрокаМатериалы.Номенклатура);
    НайденныеСтроки = ТаблицаМатериалов.НайтиСтроки(ПараметрыОтбора);
    
    // Если по какой-то причине партии для номенклатуры не найдены, переходим к следующей строке
    Если НайденныеСтроки.Количество() = 0 Тогда
        Продолжить;
    КонецЕсли;
    
    СтрокаМатериалов = НайденныеСтроки[0];
    
    // Шаг 2. Обязательно проверяем тип данных во избежание падения системы при пустых значениях
    Если ТипЗнч(СтрокаМатериалов.СписанныеПартииБУ) = Тип("ТаблицаЗначений") Тогда
        
        // Шаг 3. Запускаем обход вложенной таблицы списанных партий конкретного материала
        Для Каждого ТекущСтр Из СтрокаМатериалов.СписанныеПартииБУ Цикл
            
            // Создаем запись движения строго ВНУТРИ цикла по партиям
            Движение = Движения.Запчасти.Добавить();
            Движение.Организация = Организация;
            Движение.Водитель = Водитель;
            Движение.Номенклатура = ТекСтрокаМатериалы.Номенклатура;
            Движение.Автомобиль = Автомобиль;
            Движение.Документ = ЭтотОбъект.Ссылка;
            
            // Распределяем количество и сумму конкретной партии
            Движение.Количество = ТекущСтр.Количество; 
            Движение.Сумма = ТекущСтр.СуммаСписания;
            
        КонецЦикла;
        
    КонецЕсли;
КонецЦикла;

Разберем подробнее, за счет чего этот алгоритм работает корректно. Метод НайтиСтроки() позволяет нам мгновенно сопоставить текущую позицию номенклатуры с нужной строкой из ТаблицаМатериалов. После этого мы точечно обращаемся к ячейке СписанныеПартииБУ, которая содержит вложенную таблицу значений. Создание нового движения методом Добавить() происходит непосредственно в момент итерации по строкам этой вложенной таблицы. Сколько строк партий содержится во вложенной таблице, столько строк движений и будет сформировано в регистре, при этом каждая строка получит свою собственную сумму.


Вариант решения 2. Сворачивание данных и получение общей суммы (Свернутый учет)

Довольно часто детальный учет по партиям в самом регистре «Запчасти» не требуется. Регистр может быть предназначен только для контроля общих затрат в разрезе номенклатуры и автомобилей. В этом случае создавать несколько движений по каждой партии нецелесообразно — это приведет к избыточному росту базы данных.

Для решения этой задачи нам вообще не нужно писать вложенные циклы для обхода партий. Мы можем воспользоваться высокоэффективным встроенным методом платформы 1С — Итог(). Этот метод реализован на уровне платформенного C++ кода и работает значительно быстрее любого ручного перебора строк в цикле.

Посмотрим на пример реализации свернутого учета:


Движения.Запчасти.Записывать = Истина;

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

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


Важные практические рекомендации и подводные камни

При проектировании механизмов, использующих вложенные таблицы значений, крайне важно соблюдать несколько правил разработки, которые помогут избежать труднодиагностируемых ошибок в будущем:

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

← На главную