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