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