В процессе разработки на платформе 1С:Предприятие часто возникает задача: при проведении документа (например, «Поступление товаров и услуг») необходимо определить, на основании какого именно документа он был создан, и в зависимости от этого выполнить специфические движения или проверки. В типовых конфигурациях, особенно со сложной или устаревшей архитектурой (таких как «Комплексная автоматизация 1.1» или «Управление производственным предприятием 1.1»), явного реквизита ДокументОснование в шапке может не существовать.
В этой статье мы подробно разберем, почему возникает такая сложность, рассмотрим методологическое различие между вводом на основании и подчиненностью документов, а также реализуем три практических способа решения этой задачи в зависимости от того, насколько глубоко мы можем изменять типовую конфигурацию.
Прежде чем перейти к написанию кода, проанализируем важный методологический аспект платформы 1С. Нужно четко различать два понятия:
ОбработкаЗаполнения.Если при вводе на основании мы не сохранили ссылку на документ-источник в базу данных (в реквизит шапки, табличной части или в специальный регистр сведений), то после закрытия формы или при последующих перепроведениях документа восстановить эту связь стандартными средствами платформы будет невозможно. Механизм структуры подчиненности (кнопка на панели) также не покажет связь, если ссылка на исходный документ не записана физически в реквизитах приемника.
Рассмотрим по шагам возможные варианты решения этой проблемы.
Если конфигурация находится на доработке и у нас есть возможность изменять метаданные, самым простым, надежным и производительным решением будет добавление собственного реквизита.
Выполним следующие действия по шагам:
ПоступлениеТоваровИУслуг новый реквизит шапки ДокументОснование с составным типом данных (включающим те документы, которые могут выступать основанием, например, ДокументСсылка.ЗаказПоставщику).ОбработкаЗаполнения. Напишем код, который будет фиксировать ссылку на основание при создании документа:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
// Проверяем, что документ заполняется именно на основании Заказа поставщику
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПоставщику") Тогда
ЭтотОбъект.ДокументОснование = ДанныеЗаполнения;
КонецЕсли;
КонецПроцедуры
Теперь при проведении документа в процедуре ОбработкаПроведения мы можем легко получить доступ к документу-основанию и выполнить необходимую бизнес-логику:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Проверяем наличие и тип документа-основания
Если ЗначениеЗаполнено(ЭтотОбъект.ДокументОснование)
И ТипЗнч(ЭтотОбъект.ДокументОснование) = Тип("ДокументСсылка.ЗаказПоставщику") Тогда
// Выполняем специфические действия для поступления по заказу
ПровестиПоступлениеПоЗаказнойСхеме(Отказ);
КонецЕсли;
КонецПроцедуры
Преимущества: Простота реализации, высокая скорость работы, стандартное отображение связи в дереве подчиненности (при условии включения реквизита в соответствующий критерий отбора).
Недостатки: Требует изменения структуры метаданных типового документа, что может усложнить последующие обновления конфигурации.
Что делать, если конфигурация должна оставаться максимально типовой, и добавлять новые реквизиты в документы запрещено правилами поддержки? Мы можем использовать связку из механизма ДополнительныеСвойства объекта и независимого регистра сведений.
Идея заключается в следующем: в момент заполнения документа мы запоминаем основание в оперативной памяти объекта, а при записи документа фиксируем эту связь в отдельном регистре сведений.
Разберем реализацию этого механизма:
СвязиДокументовОснований.
ДокументПриемник (тип: ДокументСсылка.ПоступлениеТоваровИУслуг).ДокументОснование (тип: ДокументСсылка.ЗаказПоставщику).ПоступлениеТоваровИУслуг в процедуре ОбработкаЗаполнения передаем ссылку во временную структуру ДополнительныеСвойства. На этот момент ссылки у нового документа еще нет, поэтому напрямую записать в регистр мы не можем:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПоставщику") Тогда
// Передаем основание во временный контейнер объекта
ЭтотОбъект.ДополнительныеСвойства.Вставить("ВременныйДокументОснование", ДанныеЗаполнения);
КонецЕсли;
КонецПроцедуры
ПриЗаписи модуля объекта (или в соответствующей подписке на событие, чтобы не менять сам модуль документа) считываем данные из ДополнительныеСвойства. В этот момент ссылка у документа уже гарантированно создана, и мы можем сделать запись в регистр сведений:
Процедура ПриЗаписи(Отказ)
Если ЭтотОбъект.ДополнительныеСвойства.Свойство("ВременныйДокументОснование") Тогда
Основание = ЭтотОбъект.ДополнительныеСвойства.ВременныйДокументОснование;
// Записываем связь в регистр сведений
МенеджерЗаписи = РегистрыСведений.СвязиДокументовОснований.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ДокументПриемник = ЭтотОбъект.Ссылка;
МенеджерЗаписи.ДокументОснование = Основание;
Попытка
МенеджерЗаписи.Записать(Истина);
Исключение
ЗаписьЖурналаРегистрации("СвязиДокументов", УровеньЖурналаРегистрации.Ошибка, , ,
ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Процедура ОбработкаПроведения(Отказ, ModeПроведения)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СвязиДокументов.ДокументОснование КАК ДокументОснование
|ИЗ
| РегистрСведений.СвязиДокументовОснований КАК СвязиДокументов
|ГДЕ
| СвязиДокументов.ДокументПриемник = &ДокументПриемник";
Запрос.УстановитьПараметр("ДокументПриемник", ЭтотОбъект.Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Основание = Выборка.ДокументОснование;
// Обрабатываем проведение с учетом найденного документа-основания
ПровестиПо СхемеЗаказа(Основание, Отказ);
КонецЕсли;
КонецПроцедуры
Преимущества: Позволяет обойтись без изменения структуры метаданных самого документа (все изменения можно вынести в подписки на события и расширение конфигурации), сохраняя типовое решение на полной поддержке.
Недостатки: Требуется администрирование дополнительного регистра сведений, а также контроль за удалением записей при удалении документов.
Давайте проанализируем, как устроена связь документов в типовой конфигурации «Комплексная автоматизация 1.1» без наших доработок. В зависимости от настроек ведения учета в Договоре контрагента связь с заказами может храниться в разных местах.
Если в договоре с поставщиком выбран вариант ведения взаиморасчетов «По заказам», то поле Сделка в шапке документа поступления становится обязательным для заполнения. В качестве сделки там как раз и выступает Заказ поставщику. В этом случае задача решается максимально просто — анализом шапки документа:
Если ЗначениеЗаполнено(ЭтотОбъект.Сделка)
И ТипЗнч(ЭтотОбъект.Сделка) = Тип("ДокументСсылка.ЗаказПоставщику") Тогда
// Документ оформлен в рамках Заказа поставщику
ЗаказОснование = ЭтотОбъект.Сделка;
КонецЕсли;
Если взаиморасчеты ведутся по договору в целом, поле Сделка может отсутствовать или быть не заполненным. Однако связь с заказами сохраняется в табличной части Товары в колонке ЗаказПоставщика (или просто Заказ). При вводе на основании заказа табличная часть автоматически заполняется строками, где эта колонка содержит ссылку на исходный заказ.
Мы можем программно проанализировать табличную часть при проведении:
// Выгружаем колонку заказов из табличной части товаров
ТаблицаЗаказов = ЭтотОбъект.Товары.ВыгрузитьКолонку("ЗаказПоставщика");
// Очищаем массив от пустых значений и дублей
МассивЗаказов = Новый Массив;
Для Каждого ТекущийЗаказ Из ТаблицаЗаказов Цикл
Если ЗначениеЗаполнено(ТекущийЗаказ)
И ТипЗнч(ТекущийЗаказ) = Тип("ДокументСсылка.ЗаказПоставщику")
И МассивЗаказов.Найти(ТекущийЗаказ) = Неопределено Тогда
МассивЗаказов.Добавить(ТекущийЗаказ);
КонецЕсли;
КонецЦикла;
// Анализируем результат
Если МассивЗаказов.Количество() > 0 Тогда
// Документ содержит строки, привязанные к заказам поставщика
Для Каждого ЗаказИзТЧ Из МассивЗаказов Цикл
// Выполняем логику проведения по каждому уникальному заказу
ОбработатьПроведениеПоЗаказу(ЗаказИзТЧ, Отказ);
КонецЦикла;
КонецЕсли;
Важное предупреждение: Анализ табличной части не дает 100% гарантии того, что документ был создан именно по кнопке «Ввести на основании». Пользователь мог создать документ вручную, а затем подобрать строки из заказа или заполнить их вручную. Тем не менее, с точки зрения управленческого и оперативного учета, ключевое значение имеет именно фактическая привязка номенклатурных позиций к заказам в табличной части, поэтому данный способ является стандартным для большинства типовых механизмов 1С.