Лень – двигатель прогресса. Эту фразу любила повторять моя школьная преподаватель математики, и по совместительству директор школы, когда приводила примеры решения одной и той же задачи разными путями, один из которых выглядел много проще и короче остальных. Работа программиста в этот принцип вписывается как нельзя лучше. По большому счету, мы стремимся, чтобы система учета работала как часы, потому что лень заниматься поддержкой пользователей. Ведь гораздо удобнее, когда пользователь в поддержке не нуждается и не отвлекает лишний раз от более серьезных занятий, чем разбирательства с ошибкой в данных. Мы стремимся делать наш код удобочитаемым, потому что лень в него вчитываться, пытаясь понять, что же все-таки там происходит. В конечном итоге от лени мы стараемся дробить свой код на процедуры и функции так, чтобы его можно было использовать в последующих разработках уже не заморачиваясь отладкой куска, который всегда отлично работал и продолжает работать. И именно от лени многие из нас создают разного рода универсализмы.
Эту статью меня сподвиг написать один из посетителей Инфостарта, когда в комментариях темы о полезных функциях и процедурах я забросил текст небольшого модуля для подключения к управляемым формам объектов данных, связанных с этими объектами и хранимых в регистрах сведений. Почти сразу нашелся желающий прочесть подробнее и в отдельной статье, поэтому решил немножко потрудиться на благо публики и расписать подробнее. Актуально это решение в первую очередь для нетиповых конфигураций или подсистем, имеющихся в нашем распоряжении.
Без регистров сведений сегодня сложно представить какую-либо конфигурацию. Эта довольно несложная для понимания группа метаданных одновременно представляет собой мощное средство для решения возникающих у 1С-ника задач. Реквизиты компаний имеют свойство иногда меняться? – смотрим в сторону Регистров сведений. Хотим учитывать подходящую к оборудованию номенклатуру запчастей? Снова к регистрам сведений. И не предлагайте табличные части, ведь менеджер, который закупает запчасти, тоже захочет оценить куда они подходят, когда сообразит, что ему это весьма полезно. Вот тогда с табличной частью-то и намучаемся.
Есть и менее очевидные вещи – если пересадить в 1С фанатичных любителей Excel, независимо от того добровольно для них это было или нет, очень быстро появляются те, кому не нравится для редактирования документов открывать их в отдельной форме. Помучавшись с чтением / записью этих документов при активизации строки скоро начинаешь понимать – а ведь проще работая с такими господами ничего кроме номера, даты и пары реквизитов, которые в здравом уме после его создания никто не соберется править, в самом документе не иметь (о документе говорим нестандартном, конечно же) а всё, что господа там дозаполняют по 20 раз на день вынести все в тот же регистр сведений. Ну а если господа выдумали некую группу показателей, которую даже теоретически должны видеть только три человека, в то время как все остальные – половина пользователей, сами Боги велели вынести эти показатели в отдельную табличку, и естественно это будет регистр сведений.
Еще одно из решений, связанных с использованием регистров сведений, к которому пришел я (и вполне допускаю, что и еще много кто), причем задолго до появления таких технологий, как расширения, это вынесение в них нетиповой информации вместо добавления реквизитов или табличных частей к основным объектам. В свое время даже делал публикацию публикацию на эту тему.
Скрывать нечего, в моем случае наиболее богатым на всяческие дополняющие таблички оказался справочник номенклатуры. Помимо уже имеющихся из коробки УПП, касающихся его регистров сведений, добавлены еще несколько своих. Информация, хранимая в них, поделена на логически законченные блоки: один регистр хранит параметры, которые имеют значение при планировании закупок запчастей и материалов для ремонта оборудования, другой – сведения о размерах упаковок продукции предприятия, третий – необходимые для интеграции с головными предприятием параметры ну и т.д. У каждой подобной таблицы свой круг пользователей, которых интересует их участок, но соседний для них что-то слишком заумное и «лишнее». Естественно, что «лишнее» нужно прятать, а еще лучше вообще ограничить к нему доступ. Группировка данных в регистры сведений «по тематике» тут оказывается очень кстати. Возьмем, к примеру, такие:
Положим, со структурой данных определились, насоздавали регистров в конфигурации, но что делать с интерфейсом? Ведь пользователя, который в восторге от того, что для занесения данных нужно пробежаться по нескольким таблицам найти очень сложно. Пользователю удобно, когда все в одном окне – открыл, скажем, все ту же номенклатуру и увидел все, что о ней интересно знать, а что и как там хранится в недрах базы ну совершенно не интересно. Решение есть, и оно весьма простое. В зависимости от того имитируем для пользователя набор реквизитов объекта или его табличную часть, на форму объекта добавляем реквизиты типа «РегистрСведенийМенеджерЗаписей» или «РегистрСведенийНаборЗаписей» размещаем на форме Элементы как захочется, в событии формы «ПриСозданииНаСервере» обеспечиваем их прочтение, в событии «ПослеЗаписиНаСервере» - запись.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
ПрочитатьРазмеры();
ПрочитатьПрименимость();
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
ЗаписатьРазмеры();
ЗаписатьПрименимость();
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПрочитатьРазмеры()
Если ЗначениеЗаполнено(Номенклатура) Тогда
Запрос = Новый Запрос;
Запрос.Текст =
//Запись может быть только одна, если никто не добавит лишних измерений
"ВЫБРАТЬ ПЕРВЫЕ 1
| inf_РазмерыОбъектов.Объект,
| inf_РазмерыОбъектов.ОбъемЕдиницы,
| inf_РазмерыОбъектов.ДлинаЕдиницы,
| inf_РазмерыОбъектов.ШиринаЕдиницы,
| inf_РазмерыОбъектов.ВысотаЕдиницы,
| inf_РазмерыОбъектов.ВесБруттоУпаковки,
| inf_РазмерыОбъектов.ТипоразмерУпаковки,
| inf_РазмерыОбъектов.РядовНаПаллете,
| inf_РазмерыОбъектов.ВесНеттоСух
|ИЗ
| РегистрСведений.inf_РазмерыОбъектов КАК inf_РазмерыОбъектов
|ГДЕ
| inf_РазмерыОбъектов.Объект = &Объект"
;
Запрос.УстановитьПараметр("Объект", Номенклатура);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ЗаполнитьЗначенияСвойств(РазмерыОбъектов, Выборка);
КонецЕсли;
РазмерыОбъектов.Объект = Номенклатура;
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗаписатьРазмеры()
МенеджерЗаписи = РеквизитФормыВЗначение("РазмерыОбъектов");
МенеджерЗаписи.Объект = Номенклатура;
ЕстьЗаполненные = ПроверитьЗаполненностьРесурсовЗаписиРС(МенеджерЗаписи, "inf_РазмерыОбъектов");
Если ЕстьЗаполненные Тогда
Попытка
МенеджерЗаписи.Записать();
Исключение
КонецПопытки;
Иначе
Попытка
МенеджерЗаписи.Удалить();
Исключение
КонецПопытки;
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПрочитатьПрименимость()
НаборЗаписей = РегистрыСведений.inf_ПрименимостьНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Номенклатура);
НаборЗаписей.Прочитать();
ЗначениеВРеквизитФормы(НаборЗаписей, "Применимость");
КонецПроцедуры
&НаСервере
Процедура ЗаписатьПрименимость()
Если Не ЗначениеЗаполнено(Номенклатура) Тогда
Возврат;
КонецЕсли;
НаборЗаписей = РеквизитФормыВЗначение("Применимость");
Т = НаборЗаписей.Выгрузить();
НаборЗаписей.Отбор.Номенклатура.Установить(Номенклатура);
//НаборЗаписей.Прочитать();
НаборЗаписей.Загрузить(Т);
Для Каждого Стр Из НаборЗаписей Цикл
Стр.Номенклатура = Номенклатура;
КонецЦикла;
Попытка
НаборЗаписей.Записать();
Исключение
КонецПопытки;
КонецПроцедуры
В общем-то, для тех, кто успешно преодолел уровень новичка, тут все просто, понятно и даже рутинно. Какое-то время я так и делал. Но довольно быстро понял – подобная рутина отнимает не то, чтобы сильно много времени, но происходит это обязательно в те моменты, когда хоть какой-то результат нужно выдать вчера. Ну и банально – лень это каждый раз писать или копипастить/ поправлять, тем более после недолгой обкатки приема с регистрами сведений, их количество и области применения в конфигурации стали разрастаться, как грибы после летнего дождя. Тогда, выкроив несколько свободных часов, и взялся за очередной универсализм. Так родился небольшой модуль, позволяющий минимизировать трудозатраты на описание чтения / записи присоединенных к формам объектов регистров. Принцип потратить пару часов сейчас, но выиграть десятки потом, сработал на ура. Так родился небольшой модуль следующего содержания:
Процедура ПрочитатьНаборЗаписейРС(УпрФорма, ИмяРегистра, СтруктураОтбора, ИмяРеквизитаУФ = Неопределено) Экспорт
Если ИмяРеквизитаУФ = Неопределено Тогда
ИмяРеквизитаУФ = ИмяРегистра;
КонецЕсли;
Запрос = ПодготовитьЗапросНаЧтениеРегистраСведений(ИмяРегистра, СтруктураОтбора);
Выборка = Запрос.Выполнить().Выбрать();
НаборЗаписейУФ = УпрФорма[ИмяРеквизитаУФ];
НаборЗаписейУФ.Очистить();
Пока Выборка.Следующий() Цикл
СтрНабора = НаборЗаписейУФ.Добавить();
ЗаполнитьЗначенияСвойств(СтрНабора, Выборка);
КонецЦикла;
КонецПроцедуры
Процедура ЗаписатьНаборЗаписейРС(УпрФорма, ИмяРегистра, СтруктураОтбора,
ИмяРеквизитаУФ = Неопределено, ЗначенияЗаполнения = Неопределено) Экспорт
Если ИмяРеквизитаУФ = Неопределено Тогда
ИмяРеквизитаУФ = ИмяРегистра;
КонецЕсли;
Если ЗначенияЗаполнения = Неопределено Тогда
ЗначенияЗаполнения = Новый Структура;
КонецЕсли;
НаборЗаписей = РегистрыСведений[ИмяРегистра].СоздатьНаборЗаписей();
Для Каждого Парам Из СтруктураОтбора Цикл
НаборЗаписей.Отбор[Парам.Ключ].Установить(Парам.Значение);
КонецЦикла;
//НаборЗаписей.Прочитать();
//НаборЗаписей.Очистить();
НаборЗаписейУФ = УпрФорма[ИмяРеквизитаУФ];
Для Каждого Стр Из НаборЗаписейУФ Цикл
СтрНабора = НаборЗаписей.Добавить();
ЗаполнитьЗначенияСвойств(СтрНабора, Стр);
Для Каждого Парам Из СтруктураОтбора Цикл
СтрНабора[Парам.Ключ] = Парам.Значение;
КонецЦикла;
Для Каждого Зн Из ЗначенияЗаполнения Цикл
Если Не ЗначениеЗаполнено(СтрНабора[Зн.Ключ]) Тогда
СтрНабора[Зн.Ключ] = Зн.Значение;
КонецЕсли;
КонецЦикла;
КонецЦикла;
НаборЗаписей.Записать();
КонецПроцедуры
Процедура ПрочитатьМенеджерЗаписиРС(УпрФорма, ИмяРегистра, СтруктураОтбора, ИмяРеквизитаУФ = Неопределено) Экспорт
Если ИмяРеквизитаУФ = Неопределено Тогда
ИмяРеквизитаУФ = ИмяРегистра;
КонецЕсли;
МенеджерЗаписиУФ = УпрФорма[ИмяРеквизитаУФ];
Запрос = ПодготовитьЗапросНаЧтениеРегистраСведений(ИмяРегистра, СтруктураОтбора);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ЗаполнитьЗначенияСвойств(МенеджерЗаписиУФ, Выборка);
Иначе
ПустойМенеджерЗаписи = РегистрыСведений[ИмяРегистра].СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МенеджерЗаписиУФ, ПустойМенеджерЗаписи);
КонецЕсли;
КонецПроцедуры
Процедура ЗаписатьМенеджерЗаписейРС(УпрФорма, ИмяРегистра, СтруктураОтбора,
ИмяРеквизитаУФ = Неопределено, ЗначенияЗаполнения = Неопределено) Экспорт
ПроверитьЗаполнение = СтруктураНеключевыхПолейРС(ИмяРегистра, СтруктураОтбора);
Если ИмяРеквизитаУФ = Неопределено Тогда
ИмяРеквизитаУФ = ИмяРегистра;
КонецЕсли;
Если ЗначенияЗаполнения = Неопределено Тогда
ЗначенияЗаполнения = Новый Структура;
КонецЕсли;
МенеджерЗаписиУФ = УпрФорма[ИмяРеквизитаУФ];
МенеджерЗаписи = РегистрыСведений[ИмяРегистра].СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МенеджерЗаписи, МенеджерЗаписиУФ);
Для Каждого Парам Из СтруктураОтбора Цикл
МенеджерЗаписи[Парам.Ключ] = Парам.Значение;
КонецЦикла;
Для Каждого Зн Из ЗначенияЗаполнения Цикл
Если Не ЗначениеЗаполнено(МенеджерЗаписи[Зн.Ключ]) Тогда
МенеджерЗаписи[Зн.Ключ] = Зн.Значение;
КонецЕсли;
КонецЦикла;
Писать = Ложь;
Для Каждого Поле Из ПроверитьЗаполнение Цикл
Если ЗначениеЗаполнено(МенеджерЗаписи[Поле.Ключ]) Тогда
Писать = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Если Писать Тогда
МенеджерЗаписи.Записать();
Иначе
МенеджерЗаписи.Удалить();
КонецЕсли;
КонецПроцедуры
//ИмяРегистра - Имя регистра сведений, для которого читается набор
//СтруктураОтбора - Значения измерений, по которым фильтруются записи
//МенеджерЗаписи - При чтении в менеджер записи - реквизит формы,
Функция ПодготовитьЗапросНаЧтениеРегистраСведений(ИмяРегистра, СтруктураОтбора)
Запрос = Новый Запрос;
ТекстЗапроса =
"ВЫБРАТЬ
|
|&Поля
|
|ИЗ РегистрСведений.&ИмяРегистра КАК Т
|
|ГДЕ &УсловияОтбора
|"
;
ТекстПоля = "";
МетаданныеРС = Метаданные.РегистрыСведений[ИмяРегистра];
Для Каждого Изм Из МетаданныеРС.Измерения Цикл
ТекПоле = "Т." + Изм.Имя;
Если ТекстПоля = "" Тогда
ТекстПоля = ТекПоле;
Иначе
ТекстПоля = ТекстПоля + "," + Символы.ПС + ТекПоле;
КонецЕсли;
КонецЦикла;
Для Каждого Рес Из МетаданныеРС.Ресурсы Цикл
ТекПоле = "Т." + Рес.Имя;
Если ТекстПоля = "" Тогда
ТекстПоля = ТекПоле;
Иначе
ТекстПоля = ТекстПоля + "," + Символы.ПС + ТекПоле;
КонецЕсли;
КонецЦикла;
Для Каждого Рекв Из МетаданныеРС.Реквизиты Цикл
ТекПоле = "Т." + Рекв.Имя;
Если ТекстПоля = "" Тогда
ТекстПоля = ТекПоле;
Иначе
ТекстПоля = ТекстПоля + "," + Символы.ПС + ТекПоле;
КонецЕсли;
КонецЦикла;
Если МетаданныеРС.ПериодичностьРегистраСведений <> Метаданные.СвойстваОбъектов.ПериодичностьРегистраСведений.Непериодический Тогда
ТекПоле = "Т.Период";
Если ТекстПоля = "" Тогда
ТекстПоля = ТекПоле;
Иначе
ТекстПоля = ТекстПоля + "," + Символы.ПС + ТекПоле;
КонецЕсли;
КонецЕсли;
ТекстУсловияОтбора = "";
Для Каждого Парам Из СтруктураОтбора Цикл
ТекОтбор = "Т." + Парам.Ключ + " = &" + Парам.Ключ;
Если ТекстУсловияОтбора = "" Тогда
ТекстУсловияОтбора = ТекОтбор;
Иначе
ТекстУсловияОтбора = ТекстУсловияОтбора + Символы.ПС + " И " + ТекОтбор;
КонецЕсли;
Запрос.УстановитьПараметр(Парам.Ключ, Парам.Значение);
КонецЦикла;
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&Поля" , ТекстПоля);
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ИмяРегистра" , ИмяРегистра);
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&УсловияОтбора" , ТекстУсловияОтбора);
Запрос.Текст = ТекстЗапроса;
Возврат Запрос;
КонецФункции
Функция СтруктураНеключевыхПолейРС(ИмяРегистра, СтруктураОтбора)
НеключевыеРеквизиты = Новый Структура;
МетаданныеРС = Метаданные.РегистрыСведений[ИмяРегистра];
Для Каждого Изм Из МетаданныеРС.Измерения Цикл
Если Не СтруктураОтбора.Свойство(Изм.Имя) Тогда
НеключевыеРеквизиты.Вставить(Изм.Имя);
КонецЕсли;
КонецЦикла;
Для Каждого Рес Из МетаданныеРС.Ресурсы Цикл
Если Не СтруктураОтбора.Свойство(Рес.Имя) Тогда
НеключевыеРеквизиты.Вставить(Рес.Имя);
КонецЕсли;
КонецЦикла;
Для Каждого Рекв Из МетаданныеРС.Реквизиты Цикл
Если Не СтруктураОтбора.Свойство(Рекв.Имя) Тогда
НеключевыеРеквизиты.Вставить(Рекв.Имя);
КонецЕсли;
КонецЦикла;
Если МетаданныеРС.ПериодичностьРегистраСведений <> Метаданные.СвойстваОбъектов.ПериодичностьРегистраСведений.Непериодический Тогда
НеключевыеРеквизиты.Вставить("Период");
КонецЕсли;
Возврат НеключевыеРеквизиты;
КонецФункции
Вызывается это так:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
СтруктураОтбора = Новый Структура("Номенклатура", Номенклатура);
inf_РаботаСДиалогамиСервер.ПрочитатьНаборЗаписейРС(ЭтаФорма, "вичи_ПрименимостьНоменклатуры",
СтруктураОтбора, "Применимость");
СтруктураОтбора2 = Новый Структура("Объект", Номенклатура);
inf_РаботаСДиалогамиСервер.ПрочитатьМенеджерЗаписиРС(ЭтаФорма, "inf_РазмерыОбъектов",
СтруктураОтбора2, "РазмерыОбъектов");
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
СтруктураОтбора = Новый Структура("Номенклатура", Объект.Ссылка);
ЗначенияЗаполнения = Новый Структура("Назначение",
ПредопределенноеЗначение("Перечисление.inf_НазначенияИспользованияНоменклатуры.ТехническоеОбслуживание"));
inf_РаботаСДиалогамиСервер.ЗаписатьНаборЗаписейРС(ЭтаФорма, "inf_ПрименимостьНоменклатуры", СтруктураОтбора,
"Применимость", ЗначенияЗаполнения);
СтруктураОтбора2 = Новый Структура("Объект", Номенклатура);
inf_РаботаСДиалогамиСервер.ЗаписатьМенеджерЗаписейРС(ЭтаФорма, "inf_РазмерыОбъектов",
СтруктураОтбора2, "РазмерыОбъектов");
КонецПроцедуры
Основных процедур получилось четыре: ПрочитатьНаборЗаписейРС, ЗаписатьНаборЗаписейРС, ПрочитатьМенеджерЗаписиРС, ЗаписатьМенеджерЗаписейРС с почти идентичными параметрами:
- УпрФорма – форма, к которой выполняем подключение, обязательный параметр
- ИмяРегистра – Имя регистра сведений в дереве метаданных, обязательный параметр
- СтруктураОтбора – ключ структуры аналогичен имени измерения регистра, по которому требуется отфильтровать записи, значение – собственно значение, по которому фильтруем, обязательный параметр
- ИмяРеквизитаУФ – Имя реквизита на переданной в первом параметре форме, в который / из которого нужно выполнить чтение / запись, необязательный параметр, если не задан, имя реквизита будет считаться идентичным имени регистра (второй параметр)
- ЗначенияЗаполнения – есть только в процедурах записи, позволяет автоматически проставить значения ресурсов и реквизитов регистра, которыми пользователь не управляет, но они требуются нам для корректной работы системы.
Теперь получается много удобнее и быстрее. Больше не нужно расписывать очередной регистр, интегрируя его на форму, пару строк и все, что нужно прочитано и в нужный момент записано. Справедливости ради, должен заметить, что покрывая 99% подобных задач, всеобщей панацеей это решение все-таки не является. Исключением будут те редкие случаи, когда в редактировании одного и того же объекта одновременно участвуют несколько пользователей. В таком режиме остается место для ситуации, когда один из пользователей открывает объект и долго-нудно его поправляет. В это время второй открывает, быстро вносит свою лепту и сохраняет. Когда же первый заканчивает, он записывает свою версию, в том числе затерев то, что сделал его коллега. Вероятность такой ситуации всегда нужно учитывать. Если в нашей номенклатуре она нулевая, то в гипотетической системе управления задачами она вполне реальна. На те случаи выход тоже есть и заключается в использовании динамических списков с отборами, но это уже отдельная история со своей спецификой и своими подводными камнями.