Как правильно выгрузить данные из временной таблицы формы в документ 1С без ошибок и снижения производительности?

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

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

Однако при реализации этого механизма новички часто совершают ряд ошибок, из-за которых данные (например, номенклатура, контрагенты или ставки НДС) отказываются записываться, а сама база данных начинает критически «тормозить». Разберем подробно, почему возникают эти проблемы, проанализируем ошибки исходного кода и реализуем правильное решение по стандартам разработки 1С.

Выясняем причину: почему не переносятся данные?

Основная проблема, с которой сталкивается разработчик при переносе данных из экранной таблицы обратно в документ, — несоответствие типов данных (ошибка типизации). Рассмотрим ситуацию: в документе реквизит Номенклатура имеет тип ссылки (СправочникСсылка.Номенклатура), а реквизит СтавкаНДС — тип ссылки на перечисление или справочник ставок. Если во временной таблице на форме эти колонки были созданы с типом Строка, система не сможет напрямую записать строковые значения в объектную базу данных.

Платформа 1С строго типизирована. При попытке присвоить строковое значение "Кирпич красный" реквизиту с типом СправочникСсылка.Номенклатура, система проигнорирует некорректный тип и запишет в базу данных пустое значение (ПустаяСсылка). Именно поэтому у автора вопроса в коде строковые переменные содержали текст, но при записи документа поля оставались пустыми.


Анализ исходного кода: скрытые угрозы и антипаттерны

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


&НаСервере
Процедура ФунИзменения()
    ОтчОбщДляКол = Объект.Report.Товары; // Неиспользуемая переменная
    Для каждого СтрокаТовар Из Объект.ВременнаяТаблица Цикл
        ДляНом = СтрокаТовар.НоменклатураВР;
        ДляКол = СтрокаТовар.КоличествоВР;
        ДляКолУпа = СтрокаТовар.КоличествоВР;
        ДляЦен = СтрокаТовар.ЦенаВР;
        ДляСум = ДляКолУпа * ДляЦен;
        ДляНДС = СтрокаТовар.СтавкаНДСВР;
        
        // КРИТИЧЕСКАЯ ОШИБКА 1: Получение объекта базы данных внутри цикла!
        ТаблицаО = Объект.ОтчетОРозничныхПродажах.ПолучитьОбъект(); 
        
        ЗН = ТаблицаО.Товары.Добавить();
        ЗН.Склад = Объект.Склады;
        ЗН.Номенклатура = ДляНом;
        ЗН.Количество = ДляКол;
        ЗН.КоличествоУпаковок = ДляКол;
        ЗН.Цена = ДляЦен;
        ЗН.Сумма = ДляСум;
        ЗН.СтавкаНДС = ДляНДС;
        
        // КРИТИЧЕСКАЯ ОШИБКА 2: Запись и проведение документа внутри цикла!
        ТаблицаО.Записать(РежимЗаписиДокумента.Проведение); 
    КонецЦикла;
КонецПроцедуры

Проанализируем ситуацию с точки зрения производительности СУБД:

  1. Получение объекта в цикле (ПолучитьОбъект()): Метод считывает данные документа из базы данных заново на каждом шаге цикла. Если в вашей временной таблице будет 100 строк, система совершит 100 ресурсоемких обращений к СУБД, нагружая сеть и процессор сервера приложений.
  2. Запись и проведение в цикле (Записать(РежимЗаписиДокумента.Проведение)): Это самый опасный антипаттерн в мире 1С. Каждая запись инициирует транзакцию базы данных. Проведение документа запускает многочисленные обработчики проведения, делает записи в регистры накопления, регистры бухгалтерии и т.д. Выполнять это в цикле — значит гарантированно заблокировать работу всей базы данных для остальных пользователей и кратно снизить скорость работы системы. При большом объеме данных вы получите ошибку TimeOut или взаимную блокировку (Deadlock).

Разбор по шагам: пишем правильный и быстрый код

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

Шаг 1. Правильное получение и запись объекта (Оптимизация транзакций)

Запомним важнейшее правило: получать объект для изменения нужно ОДИН раз до начала цикла, а записывать (и проводить) — ОДИН раз после завершения всех манипуляций в памяти сервера.

Посмотрим на пример правильной структуры:


// Получаем объект документа ОДИН раз вне цикла
ДокументОбъект = Объект.ОтчетОРозничныхПродажах.ПолучитьОбъект();

Для каждого СтрокаТовар Из Объект.ВременнаяТаблица Цикл
    // Здесь производим заполнение строк табличной части в памяти...
КонецЦикла;

// Записываем и проводим документ ОДИН раз после завершения цикла
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);

Шаг 2. Преобразование строковых данных в ссылочные типы

Если во временной таблице значения реквизитов все же остались строковыми (например, данные загружены из файла TXT/Excel), нам необходимо преобразовать их в ссылки. На форуме было предложено использовать метод НайтиПоНаименованию(). Рассмотрим его особенности.

Использование НайтиПоНаименованию() в цикле — это рабочий, но крайне нежелательный и опасный метод в промышленной разработке:

Как сделать правильно? Лучшее решение — изначально задать для колонок временной таблицы формы правильные типы данных: СправочникСсылка.Номенклатура. В таком случае пользователь на форме будет выбирать предопределенные элементы справочника, и нам вообще не придется делать программный поиск по строке!

Если же ручной поиск неизбежен (например, при парсинге внешнего файла), то вместо поиска по наименованию рекомендуется искать товары по Артикулу или Коду. Для повышения производительности сопоставление лучше выполнять не в цикле, а одним пакетным запросом к СУБД. Но если объем данных невелик, можно использовать поиск по коду:


НайденнаяНоменклатура = Справочники.Номенклатура.НайтиПоКоду(СтрокаТовар.КодВР);
Если НЕ НайденнаяНоменклатура.Пустая() Тогда
    НоваяСтрокаДокумента.Номенклатура = НайденнаяНоменклатура;
КонецЕсли;

Шаг 3. Заполнение ставок НДС в современных конфигурациях

В старых конфигурациях 1С ставки НДС хранились в перечислениях (Перечисления.СтавкиНДС). В современных ERP-решениях (например, ERP 2.5, Комплексная автоматизация 2.5, Управление торговлей 11.5, Розница 3.0) ставки НДС вынесены в отдельный справочник Справочник.СтавкиНДС.

Чтобы избежать ошибок при обновлении системы, не следует жестко прописывать условия выбора ставок ("18%", "20%") в кодовой базе. Рекомендуется находить ставку программно по ее числовому значению. Рассмотрим пример получения ставки для современных конфигураций:


// Пример получения ставки НДС из справочника для современных конфигураций
СтавкаНДССсылка = Справочники.СтавкиНДС.НайтиПоНаименованию("20%"); 
// Или через специализированные функции БСП (Библиотеки Стандартных Подсистем)

Шаг 4. Использование функции ЗаполнитьЗначенияСвойств

Вместо того чтобы вручную переписывать каждую колонку из одной таблицы в другую, создавая длинный и громоздкий код, воспользуемся эффективным встроенным методом платформы — ЗаполнитьЗначенияСвойств().

Этот метод автоматически сопоставляет имена реквизитов источника и приемника и переносит значения только тех колонок, имена которых совпадают. Чтобы это работало идеально, назовите колонки во временной таблице точно так же, как они названы в табличной части вашего документа (например: Количество, Цена, Сумма вместо КоличествоВР, ЦенаВР).


Итоговый эталонный вариант кода

Сведем все наши архитектурные улучшения в один красивый, быстрый и безопасный алгоритм проведения изменений:


&НаСервере
Процедура ВыгрузитьИзВременнойТаблицыВДокумент()
    
    // Шаг 1. Получаем объект документа ОДИН раз вне цикла
    ДокументОбъект = Объект.ОтчетОРозничныхПродажах.ПолучитьОбъект();
    
    // Дополнительно: если нужно полностью перезаписать табличную часть, предварительно очистим ее
    ДокументОбъект.Товары.Очистить();
    
    // Шаг 2. Обходим временную таблицу формы в цикле
    Для каждого СтрокаВР Из Объект.ВременнаяТаблица Цикл
        
        // Добавляем новую строку в табличную часть документа
        НоваяСтрока = ДокументОбъект.Товары.Добавить();
        
        // Шаг 3. Быстро заполняем совпадающие реквизиты (Количество, Цена, Сумма и т.д.)
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаВР);
        
        // Заполняем склад из шапки формы, если необходимо
        НоваяСтрока.Склад = Объект.Склады;
        
        // Шаг 4. Безопасно сопоставляем ссылочные типы данных, если на форме были строки
        Если ТипЗнч(СтрокаВР.Номенклатура) = Тип("Строка") Тогда
            // Если на форме все же строка, ищем по коду (или артикулу)
            НайденныйТовар = Справочники.Номенклатура.FindByName(СтрокаВР.Номенклатура); // Альтернатива: НайтиПоКоду
            Если ЗначениеЗаполнено(НайденныйТовар) Тогда
                НоваяСтрока.Номенклатура = НайденныйТовар;
            КонецЕсли;
        Иначе
            // Если колонка формы изначально типизирована верно (СправочникСсылка.Номенклатура)
            НоваяСтрока.Номенклатура = СтрокаВР.Номенклатура;
        EndIf;
        
        // Шаг 5. Заполнение Ставки НДС (пример с учетом современных конфигураций)
        Если ТипЗнч(СтрокаВР.СтавкаНДС) = Тип("Строка") Тогда
            // Программный поиск ставки в справочнике СтавкиНДС
            НоваяСтрока.СтавкаНДС = Справочники.СтавкиНДС.НайтиПоНаименованию(СтрокаВР.СтавкаНДС);
        Иначе
            НоваяСтрока.СтавкаНДС = СтрокаВР.СтавкаНДС;
        КонецЕсли;
        
    КонецЦикла;
    
    // Шаг 6. Записываем измененный документ с проведением один раз после цикла
    Попытка
        ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        Сообщить("Документ успешно перезаписан и проведен!");
    Исключение
        // Обработка исключений в случае неуспешного проведения (например, нехватка остатков)
        Сообщить("Ошибка при записи документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
    КонецПопытки;
    
КонецПроцедуры

Подведем итоги

Для качественной разработки в системе «1С:Предприятие 8» всегда придерживайтесь следующих правил проектирования:

Используйте строгую типизацию колонок таблиц формы на этапе создания интерфейса в конфигураторе. Это избавит вас от необходимости писать тяжелые функции поиска ссылок по текстовым представлениям. Никогда не помещайте вызовы методов ПолучитьОбъект() и Записать() внутрь циклов — всегда обрабатывайте коллекции данных в оперативной памяти сервера и фиксируйте транзакции единовременно.

← На главную