Как реализовать универсальную запись периодического реквизита из контекста формы в 1С:Предприятие 7.7

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Разработка универсальных алгоритмов в среде 1С:Предприятие 7.7 всегда требовала от программиста глубокого понимания внутренней архитектуры платформы. Одной из классических задач, с которой сталкиваются разработчики при конфигурировании справочников, является управление историей периодических реквизитов.

В отличие от обычных реквизитов, периодические хранят историю своих изменений с привязкой к датам. При интерактивной работе в форме элемента справочника стандартное поведение платформы часто заставляет пользователя отвечать на назойливые вопросы о дате сохранения изменений. Но что делать, если перед нами стоит задача написать красивую, элегантную и, главное, универсальную процедуру для изменения любого периодического реквизита любого справочника и вызывать ее из глобального модуля? Рассмотрим подробно, как обойти ограничения платформы 1С 7.7, почему стандартный код авторов часто не работает, и как реализовать надежное универсальное решение.

Анализ исходных ошибок: почему не работает простой код?

Выясним причину, по которой простая попытка записать значение в периодический реквизит «в лоб» из глобального модуля терпит неудачу. Проанализируем ключевые ошибки в исходных рассуждениях:

  1. Отсутствие контекста формы в Глобальном модуле. Если вы вызываете процедуру из глобального модуля, платформа по умолчанию не знает, с какой именно формой и каким элементом справочника происходит работа. Попытка вызвать метод ТекущийЭлемент() без явной передачи контекста открытой формы вернет ошибку.
  2. Проблема блокировки открытого объекта. При попытке найти текущий элемент через метод НайтиЭлемент() и изменить его реквизит, система может заблокировать операцию, поскольку данный элемент уже открыт пользователем на экране и находится в режиме редактирования.
  3. Жесткая типизация и отсутствие динамической подстановки. Попытка написать код вида Сотр.ИмяР.Установить(...) в 1С 7.7 принципиально неработоспособна. Платформа будет искать у объекта справочника реквизит с физическим именем "ИмяР", а не подставит значение, хранящееся в этой переменной. В отличие от версии 8.х, платформа 7.7 не умеет напрямую обращаться к свойствам объекта через квадратные скобки вида Спр[ИмяРеквизита].

Решение проблемы через объект «Периодический»

Для создания действительно универсальной процедуры нам понадобится специализированный системный объект — Периодический. Этот объект идеально подходит для подобных задач. Его главное преимущество заключается в методе ИспользоватьОбъект(), который умеет принимать имя периодического реквизита в виде обычной текстовой строки, а также ссылку на сам элемент справочника.

Проанализируем ситуацию: передавая имя реквизита строкой, мы полностью уходим от жесткого кодирования идентификаторов реквизитов. Это позволяет нам написать одну универсальную процедуру в глобальном модуле и использовать её для любых справочников конфигурации.

Универсальный код для Глобального модуля

Рассмотрим по шагам реализацию универсальной процедуры в Глобальном модуле (обязательно укажем ключевое слово Экспорт):


Процедура глИзменитьПериодическийРеквизит(Конт, ИмяРеквизита) Экспорт
    // 1. Проверяем, передан ли контекст формы
    Если ТипЗначения(Конт) <> 100 Тогда
        Предупреждение("Неверно передан контекст формы в универсальную процедуру!");
        Возврат;
    КонецЕсли;

    // 2. Ловушка нового элемента: периодические значения нельзя писать для незаписанного объекта
    Если Конт.ТекущийЭлемент().Выбран() = 0 Тогда
        Если Вопрос("Для установки периодического реквизита необходимо сначала записать элемент. Записать?", "Да+Нет") = "Да" Тогда
            Конт.Записать();
        Иначе
            Возврат;
        КонецЕсли;
    КонецЕсли;
    
    // 3. Запрашиваем дату начала действия периодического значения
    ВыбДата = РабочаяДата(); 
    Если ВвестиДату(ВыбДата, "С какой даты установить значение реквизита '" + ИмяРеквизита + "'?", 0) = 0 Тогда
        // Пользователь отказался от ввода даты, прерываем выполнение
        Возврат;
    КонецЕсли;
    
    // 4. Получаем измененное значение реквизита непосредственно из контекста формы справочника
    НовоеЗначение = Конт.ПолучитьАтрибут(ИмяРеквизита);
    
    // 5. Инициализируем объект "Периодический" для прямой записи в базу данных
    Периодич = СоздатьОбъект("Периодический");
    
    // Передаем имя реквизита строкой и ссылку на текущий элемент
    Периодич.ИспользоватьОбъект(ИмяРеквизита, Конт.ТекущийЭлемент());
    Периодич.ДатаЗнач = ВыбДата;
    Периодич.Значение = НовоеЗначение;
    
    Попытка
        Периодич.Записать();
        // Принудительно обновляем форму, чтобы изменения сразу отразились на экране
        Конт.Обновить(0);
    Исключение
        Предупреждение("Не удалось записать периодическое значение! Возможно, объект заблокирован.");
    КонецПопытки;
КонецПроцедуры

Как настроить вызов процедуры из формы справочника

Рассмотрим, как правильно привязать эту процедуру к элементам управления формы. Для этого выполним следующие действия:

  1. Откройте конфигуратор, найдите нужный справочник и откройте его форму элемента.
  2. Найдите поле ввода, связанное с периодическим реквизитом (например, "Должность" или "Оклад"), и откройте его свойства.
  3. В строке "Формула" пропишите вызов нашей глобальной функции, передавая ключевое слово Контекст (которое передает управление формой) и строковое имя реквизита: глИзменитьПериодическийРеквизит(Контекст, "Должность")

При изменении значения в данном поле система автоматически обратится к нашей глобальной процедуре, корректно запросит дату и запишет новое значение в историю реквизита.

Важные подводные камни и способы их обхода

При интеграции подобных решений важно учитывать неочевидные особенности платформы 1С 7.7. Разберем две важные проблемы:

Конфликт двойного сохранения (Проблема интерактивного диалога). Когда пользователь изменяет значение на форме, платформа фиксирует интерактивную модификацию поля в буфере экрана. Поскольку мы уже программно записали это значение в базу данных через объект Периодический, при последующем закрытии формы по кнопке "ОК" платформа обнаружит, что реквизит на форме был изменен. Система повторно выведет стандартный диалог с вопросом: "Изменились периодические реквизиты. Записать?".

Чтобы подавить этот стандартный механизм и избежать двойного запроса даты, в модуле формы справочника в предопределенной процедуре ПриЗаписи() необходимо использовать метод платформы:

СохранениеПериодическихРеквизитов(4);

Параметр 4 сообщает системе, что измененные периодические реквизиты сохранять не требуется (так как мы это уже сделали программно в нашей процедуре).

Динамическая работа с метаданными

Для повышения отказоустойчивости нашей глобальной процедуры полезно предварительно анализировать структуру метаданных. Посмотрим на пример вспомогательных функций, которые позволяют проверить, существует ли реквизит в справочнике и является ли он периодическим:


Функция глЕстьРеквизитВСправочнике(ИмяРеквизита, ВидСправочника) Экспорт
    Статус = 0;
    Если ПустоеЗначение(ВидСправочника) = 0 Тогда
        // Определяем вид, если передана ссылка на объект справочника
        ВидСпр = ?(ТипЗначения(ВидСправочника) = 11, ВидСправочника.Вид, ВидСправочника);
        
        Если ПустоеЗначение(ИмяРеквизита) = 0 Тогда
            // Проверяем наличие реквизита в метаданных справочника
            Если Метаданные.Справочник(ВидСпр).Реквизит(ИмяРеквизита).Выбран() = 1 Тогда
                Статус = 1;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
    Возврат Статус;
КонецФункции

Используя этот подход, мы можем динамически считывать значения периодических реквизитов на определенную дату без прямого обращения к их именам в коде:


Функция глПолучитьРеквизитЭлементаСправочника(Элемент, ИмяРеквизита, НаДату = 0) Экспорт
    Если (ТипЗначения(Элемент) <> 11) Или (ПустоеЗначение(Элемент) = 1) Тогда
        Возврат "";
    КонецЕсли;
    
    ВидСпр = Элемент.Вид;
    Если глЕстьРеквизитВСправочнике(ИмяРеквизита, ВидСпр) = 0 Тогда
        Возврат "";
    КонецЕсли;
    
    // Проверяем, является ли реквизит периодическим
    Если Метаданные.Справочник(ВидСпр).Реквизит(ИмяРеквизита).Периодический = 1 Тогда
        Если Элемент.ИспользоватьДату = Дата(0) Тогда
            ДатаПроверки = ?(НаДату = 0, ТекущаяДата(), НаДату);
            Возврат Элемент.ПолучитьАтрибут(ИмяРеквизита).Получить(ДатаПроверки);
        Иначе
            Возврат Элемент.ПолучитьАтрибут(ИмяРеквизита);
        КонецЕсли;
    КонецЕсли;
    
    // Для непериодических реквизитов возвращаем обычное значение
    Возврат Элемент.ПолучитьАтрибут(ИмяРеквизита);
КонецФункции

Альтернатива: использование типового механизма конфигураций

Проанализируем ситуацию: стоит ли писать собственную процедуру, если вы работаете с типовым решением? В большинстве типовых конфигураций 1С 7.7 (таких как "Бухгалтерский учет" или "Торговля и Склад") уже реализован комплексный механизм ведения истории периодических реквизитов.

Вместо мгновенной записи каждого реквизита при его изменении, типовые решения накапливают изменения в буфере формы, а при записи элемента (по кнопке "ОК" или "Записать") вызывают глобальную функцию:

глЗаписьПериодическихРеквизитов(Контекст)

Эта функция открывает специализированное диалоговое окно (обработку "ЗаписьПериодическихРеквизитов"), где в удобной таблице перечисляются все измененные реквизиты, и пользователь может одной датой подтвердить изменения для всех полей сразу. Если вы дорабатываете типовую конфигурацию, рекомендуется использовать именно этот штатный механизм для сохранения единообразия логики интерфейса.

← На главную