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