Разработка в системе «1С:Предприятие 8» часто ставит перед начинающими специалистами задачи, которые кажутся интуитивно понятными, но при первой же попытке реализации вызывают массу трудностей. Типичный пример — необходимость перенести текстовый реквизит из строки табличной части документа-источника в аналогичную строку нового документа, создаваемого на его основании.
Стандартные учебники часто описывают лишь использование простого «Конструктора копирования», который работает без сбоев только в простейших учебных базах. На практике, работая с реальными конфигурациями (такими как «1С:Бухгалтерия предприятия» или «1С:ERP»), программисты сталкиваются с тем, что механизмы заполнения уже написаны вручную, используют Библиотеку стандартных подсистем (БСП) и требуют четкого понимания взаимодействия контекстов. Рассмотрим подробнее, почему возникают популярные ошибки и как правильно реализовать эту задачу несколькими способами.
Давайте проанализируем ситуацию, описанную на форуме, и выясним причины, по которым код автора не работал. Понимание этих ошибок убережет вас от неверных архитектурных решений в будущем.
Объект.Ссылка. Автор пытался сделать запрос к базе данных, передавая в параметры ссылку на текущий создаваемый документ: Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка). Но в момент создания нового документа на основании другого, этот новый документ еще не записан в базу данных. Его ссылка в памяти компьютера пуста (точнее, представляет собой так называемую пустую ссылку данного типа). Соответственно, запрос к базе данных по пустой ссылке всегда будет возвращать нулевой результат.Объект.ТоварыИУслуги.Содержание = ... вызывает синтаксическую ошибку выполнения. Поле ТоварыИУслуги — это не строка, а коллекция (объект типа ТабличнаяЧасть). У самой коллекции не существует свойства Содержание. Данное свойство принадлежит исключительно конкретным элементам этой коллекции — строкам (объектам типа СтрокаТабличнойЧасти). Чтобы присвоить значение, необходимо сначала спозиционироваться на нужной строке табличной части.Правильное место для программного заполнения создаваемого документа — это предопределенный обработчик событий ОбработкаЗаполнения, расположенный строго в Модуле объекта целевого документа (документа-приемника).
Давайте посмотрим на стандартный шаблон этого обработчика в модуле объекта:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Переменная ДанныеЗаполнения — это и есть наш документ-источник.
// Если документ вводится на основании другого, этот параметр содержит ссылку на документ-основание.
КонецПроцедуры
В современных конфигурациях 1С, использующих БСП, этот метод обычно вызывает общий модуль заполнения документов:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Стандартные проверки БСП
Если ДанныеЗаполнения = Неопределено Тогда
Возврат;
КонецЕсли;
// Вызов типового заполнения реквизитов БСП
ЗаполнениеДокументов.Заполнить(ЭтотОбъект, ДанныеЗаполнения);
// НАШ КОД ДОЛЖЕН НАХОДИТЬСЯ ЗДЕСЬ
// Мы пишем логику заполнения после того, как БСП уже создала строки табличной части,
// но еще не заполнила наши нетиповые реквизиты.
КонецПроцедуры
Рассмотрим самый простой и частый сценарий. Нам нужно заполнить реквизит Содержание в табличной части нового документа на основе соответствующей строки из табличной части исходного документа. При этом мы уверены, что структура и порядок строк в документах полностью идентичны (так как типовой механизм БСП уже скопировал строки «один в один»).
Для реализации этого сценария мы применим обход строк по индексу. Разберем этот пример кода:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Сначала даем отработать базовому коду конфигурации
ЗаполнениеДокументов.Заполнить(ЭтотОбъект, ДанныеЗаполнения);
// Проверяем, что документ заполняется именно на основании Реализации товаров и услуг
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
// Организуем цикл по строкам табличной части НАШЕГО нового документа
Для Индекс = 0 По ЭтотОбъект.Товары.Количество() - 1 Цикл
// На всякий случай проверяем, что в исходном документе есть строка с таким же индексом
Если Индекс < ДанныеЗаполнения.Товары.Количество() Тогда
// Получаем строку из документа-источника (Реализации)
СтрокаИсточник = ДанныеЗаполнения.Товары[Индекс];
// Получаем соответствующую строку в текущем документе
СтрокаПриемник = ЭтотОбъект.Товары[Индекс];
// Переносим значение нашего текстового реквизита "Содержание"
СтрокаПриемник.Содержание = СтрокаИсточник.Содержание;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Что делать, если в документе-получателе состав строк может отличаться от документа-источника? Например, пользователь при вводе на основании мог частично удалить строки или изменить их порядок. В таком случае метод поиска по индексу приведет к тому, что тексты скопируются некорректно или запишутся не в те товары.
Для надежного сопоставления строк проанализируем ситуацию и выполним поиск по ключевому полю — номенклатурной позиции (реквизиту Номенклатура):
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
ЗаполнениеДокументов.Заполнить(ЭтотОбъект, ДанныеЗаполнения);
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
// Обходим каждую строку нашей табличной части
Для Каждого СтрокаПриемник Из ЭтотОбъект.Товары Цикл
// Формируем структуру отбора по ключевому реквизиту
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Номенклатура", СтрокаПриемник.Номенклатура);
// Ищем подходящие строки в табличной части документа-источника
НайденныеСтроки = ДанныеЗаполнения.Товары.НайтиСтроки(ПараметрыОтбора);
// Если строка с такой номенклатурой найдена в исходном документе, заполняем наше поле
Если НайденныеСтроки.Количество() > 0 Тогда
СтрокаИсточник = НайденныеСтроки[0]; // Берем первую совпавшую строку
СтрокаПриемник.Содержание = СтрокаИсточник.Содержание;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Поскольку автор исходного вопроса упомянул, что разработка ведется в типовой конфигурации «1С:Бухгалтерия», крайне не рекомендуется вносить изменения непосредственно в код основной конфигурации. Это усложнит процедуру последующих обновлений программы. Правильным и профессиональным подходом будет использование Расширений конфигурации.
Давайте разберем по шагам, как решить нашу задачу через механизм расширений:
ОбработкаЗаполнения с аннотацией &После. Это позволит выполнить ваш собственный код сразу после того, как отработают все стандартные типовые механизмы заполнения.Пример программного кода для модуля объекта в расширении будет выглядеть следующим образом:
&После("ОбработкаЗаполнения")
Процедура Ип_ОбработкаЗаполнения(ДанныеЗаполнения, СтандартнаяОбработка)
// Нам не нужно вызывать метод Заполнить() вручную, БСП уже отработала до нас.
// Наша задача — просто дописать заполнение реквизита "Содержание".
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
Для Индекс = 0 По ЭтотОбъект.Товары.Количество() - 1 Цикл
Если Индекс < ДанныеЗаполнения.Товары.Количество() Тогда
СтрокаИсточник = ДанныеЗаполнения.Товары[Индекс];
СтрокаПриемник = ЭтотОбъект.Товары[Индекс];
// Переносим данные реквизита табличной части
СтрокаПриемник.Содержание = СтрокаИсточник.Содержание;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Чтобы понять, как ведут себя переменные в процессе выполнения программы, активно используйте встроенный отладчик 1С:
ОбработкаЗаполнения.ДанныеЗаполнения и нажмите сочетание клавиш Shift + F9 (Быстрый просмотр выражений). Вы сможете раскрыть структуру документа-источника, посмотреть его табличные части и убедиться в наличии заполненных строк.Использование правильных обработчиков событий и понимание объектной модели табличных частей позволит вам писать чистый, надежный и быстродействующий код, который легко пройдет любой технический аудит.