Разработка универсальных алгоритмов в среде 1С:Предприятие 7.7 всегда требовала от программиста глубокого понимания внутренней архитектуры платформы. Одной из классических задач, с которой сталкиваются разработчики при конфигурировании справочников, является управление историей периодических реквизитов.
В отличие от обычных реквизитов, периодические хранят историю своих изменений с привязкой к датам. При интерактивной работе в форме элемента справочника стандартное поведение платформы часто заставляет пользователя отвечать на назойливые вопросы о дате сохранения изменений. Но что делать, если перед нами стоит задача написать красивую, элегантную и, главное, универсальную процедуру для изменения любого периодического реквизита любого справочника и вызывать ее из глобального модуля? Рассмотрим подробно, как обойти ограничения платформы 1С 7.7, почему стандартный код авторов часто не работает, и как реализовать надежное универсальное решение.
Выясним причину, по которой простая попытка записать значение в периодический реквизит «в лоб» из глобального модуля терпит неудачу. Проанализируем ключевые ошибки в исходных рассуждениях:
ТекущийЭлемент() без явной передачи контекста открытой формы вернет ошибку.НайтиЭлемент() и изменить его реквизит, система может заблокировать операцию, поскольку данный элемент уже открыт пользователем на экране и находится в режиме редактирования.Сотр.ИмяР.Установить(...) в 1С 7.7 принципиально неработоспособна. Платформа будет искать у объекта справочника реквизит с физическим именем "ИмяР", а не подставит значение, хранящееся в этой переменной. В отличие от версии 8.х, платформа 7.7 не умеет напрямую обращаться к свойствам объекта через квадратные скобки вида Спр[ИмяРеквизита].Для создания действительно универсальной процедуры нам понадобится специализированный системный объект — Периодический. Этот объект идеально подходит для подобных задач. Его главное преимущество заключается в методе ИспользоватьОбъект(), который умеет принимать имя периодического реквизита в виде обычной текстовой строки, а также ссылку на сам элемент справочника.
Проанализируем ситуацию: передавая имя реквизита строкой, мы полностью уходим от жесткого кодирования идентификаторов реквизитов. Это позволяет нам написать одну универсальную процедуру в глобальном модуле и использовать её для любых справочников конфигурации.
Рассмотрим по шагам реализацию универсальной процедуры в Глобальном модуле (обязательно укажем ключевое слово Экспорт):
Процедура глИзменитьПериодическийРеквизит(Конт, ИмяРеквизита) Экспорт
// 1. Проверяем, передан ли контекст формы
Если ТипЗначения(Конт) <> 100 Тогда
Предупреждение("Неверно передан контекст формы в универсальную процедуру!");
Возврат;
КонецЕсли;
// 2. Ловушка нового элемента: периодические значения нельзя писать для незаписанного объекта
Если Конт.ТекущийЭлемент().Выбран() = 0 Тогда
Если Вопрос("Для установки периодического реквизита необходимо сначала записать элемент. Записать?", "Да+Нет") = "Да" Тогда
Конт.Записать();
Иначе
Возврат;
КонецЕсли;
КонецЕсли;
// 3. Запрашиваем дату начала действия периодического значения
ВыбДата = РабочаяДата();
Если ВвестиДату(ВыбДата, "С какой даты установить значение реквизита '" + ИмяРеквизита + "'?", 0) = 0 Тогда
// Пользователь отказался от ввода даты, прерываем выполнение
Возврат;
КонецЕсли;
// 4. Получаем измененное значение реквизита непосредственно из контекста формы справочника
НовоеЗначение = Конт.ПолучитьАтрибут(ИмяРеквизита);
// 5. Инициализируем объект "Периодический" для прямой записи в базу данных
Периодич = СоздатьОбъект("Периодический");
// Передаем имя реквизита строкой и ссылку на текущий элемент
Периодич.ИспользоватьОбъект(ИмяРеквизита, Конт.ТекущийЭлемент());
Периодич.ДатаЗнач = ВыбДата;
Периодич.Значение = НовоеЗначение;
Попытка
Периодич.Записать();
// Принудительно обновляем форму, чтобы изменения сразу отразились на экране
Конт.Обновить(0);
Исключение
Предупреждение("Не удалось записать периодическое значение! Возможно, объект заблокирован.");
КонецПопытки;
КонецПроцедуры
Рассмотрим, как правильно привязать эту процедуру к элементам управления формы. Для этого выполним следующие действия:
Контекст (которое передает управление формой) и строковое имя реквизита:
глИзменитьПериодическийРеквизит(Контекст, "Должность")
При изменении значения в данном поле система автоматически обратится к нашей глобальной процедуре, корректно запросит дату и запишет новое значение в историю реквизита.
При интеграции подобных решений важно учитывать неочевидные особенности платформы 1С 7.7. Разберем две важные проблемы:
Конфликт двойного сохранения (Проблема интерактивного диалога). Когда пользователь изменяет значение на форме, платформа фиксирует интерактивную модификацию поля в буфере экрана. Поскольку мы уже программно записали это значение в базу данных через объект Периодический, при последующем закрытии формы по кнопке "ОК" платформа обнаружит, что реквизит на форме был изменен. Система повторно выведет стандартный диалог с вопросом: "Изменились периодические реквизиты. Записать?".
Чтобы подавить этот стандартный механизм и избежать двойного запроса даты, в модуле формы справочника в предопределенной процедуре ПриЗаписи() необходимо использовать метод платформы:
СохранениеПериодическихРеквизитов(4);
Параметр 4 сообщает системе, что измененные периодические реквизиты сохранять не требуется (так как мы это уже сделали программно в нашей процедуре).
Для повышения отказоустойчивости нашей глобальной процедуры полезно предварительно анализировать структуру метаданных. Посмотрим на пример вспомогательных функций, которые позволяют проверить, существует ли реквизит в справочнике и является ли он периодическим:
Функция глЕстьРеквизитВСправочнике(ИмяРеквизита, ВидСправочника) Экспорт
Статус = 0;
Если ПустоеЗначение(ВидСправочника) = 0 Тогда
// Определяем вид, если передана ссылка на объект справочника
ВидСпр = ?(ТипЗначения(ВидСправочника) = 11, ВидСправочника.Вид, ВидСправочника);
Если ПустоеЗначение(ИмяРеквизита) = 0 Тогда
// Проверяем наличие реквизита в метаданных справочника
Если Метаданные.Справочник(ВидСпр).Реквизит(ИмяРеквизита).Выбран() = 1 Тогда
Статус = 1;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Возврат Статус;
КонецФункции
Используя этот подход, мы можем динамически считывать значения периодических реквизитов на определенную дату без прямого обращения к их именам в коде:
Функция глПолучитьРеквизитЭлементаСправочника(Элемент, ИмяРеквизита, НаДату = 0) Экспорт
Если (ТипЗначения(Элемент) <> 11) Или (ПустоеЗначение(Элемент) = 1) Тогда
Возврат "";
КонецЕсли;
ВидСпр = Элемент.Вид;
Если глЕстьРеквизитВСправочнике(ИмяРеквизита, ВидСпр) = 0 Тогда
Возврат "";
КонецЕсли;
// Проверяем, является ли реквизит периодическим
Если Метаданные.Справочник(ВидСпр).Реквизит(ИмяРеквизита).Периодический = 1 Тогда
Если Элемент.ИспользоватьДату = Дата(0) Тогда
ДатаПроверки = ?(НаДату = 0, ТекущаяДата(), НаДату);
Возврат Элемент.ПолучитьАтрибут(ИмяРеквизита).Получить(ДатаПроверки);
Иначе
Возврат Элемент.ПолучитьАтрибут(ИмяРеквизита);
КонецЕсли;
КонецЕсли;
// Для непериодических реквизитов возвращаем обычное значение
Возврат Элемент.ПолучитьАтрибут(ИмяРеквизита);
КонецФункции
Проанализируем ситуацию: стоит ли писать собственную процедуру, если вы работаете с типовым решением? В большинстве типовых конфигураций 1С 7.7 (таких как "Бухгалтерский учет" или "Торговля и Склад") уже реализован комплексный механизм ведения истории периодических реквизитов.
Вместо мгновенной записи каждого реквизита при его изменении, типовые решения накапливают изменения в буфере формы, а при записи элемента (по кнопке "ОК" или "Записать") вызывают глобальную функцию:
глЗаписьПериодическихРеквизитов(Контекст)
Эта функция открывает специализированное диалоговое окно (обработку "ЗаписьПериодическихРеквизитов"), где в удобной таблице перечисляются все измененные реквизиты, и пользователь может одной датой подтвердить изменения для всех полей сразу. Если вы дорабатываете типовую конфигурацию, рекомендуется использовать именно этот штатный механизм для сохранения единообразия логики интерфейса.