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