При разработке кадровых отчетов и доработке печатных форм (например, унифицированной формы Т-7 «График отпусков») в программе «1С:Зарплата и управление персоналом 3.1» программисты часто сталкиваются с задачей: как вывести в одной таблице плановые даты отпуска из утвержденного графика и фактические даты документов Отпуск, которые были проведены в течение года.
На первый взгляд задача кажется простой — достаточно написать запрос к табличной части документа ГрафикОтпусков.Сотрудники и связать его левым соединением с документами Отпуск. Однако на практике такой подход приводит к тому, что при малейшем сдвиге дат (если сотрудник ушел в отпуск на день раньше или позже плана, либо если отпуск был перенесен) связь разрывается, и фактические данные не выводятся. В этой статье мы подробно разберем, как правильно решить эту задачу, используя механизмы платформы 1С и регистры системы.
Рассмотрим типичную ошибку, которую совершают начинающие разработчики. Попытаемся связать табличную часть документа ГрафикОтпусков с документом Отпуск напрямую по полям Сотрудник и ДатаНачала. Проанализируем пример такого запроса:
ВЫБРАТЬ
ГрафикОтпусковСотрудники.Сотрудник КАК Сотрудник,
"План" КАК ВидОтпуска,
ГрафикОтпусковСотрудники.ДатаНачала КАК ДатаНачалаПлан,
КОНЕЦПЕРИОДА(ГрафикОтпусковСотрудники.ДатаОкончания, ДЕНЬ) КАК ДатаОкончанияПлан,
ГрафикОтпусковСотрудники.КоличествоДней КАК КоличествоДнейПлан,
Отпуск.Ссылка КАК ДокументФакта,
Отпуск.ДатаНачалаОсновногоОтпуска КАК ДатаНачалаФакт,
Отпуск.ДатаОкончанияОсновногоОтпуска КАК ДатаОкончанияФакт
ИЗ
Документ.ГрафикОтпусков.Сотрудники КАК ГрафикОтпусковСотрудники
ЛЕВОЕ СОЕДИНЕНИЕ Документ.Отпуск КАК Отпуск
ПО ГрафикОтпусковСотрудники.Сотрудник = Отпуск.Сотрудник
И ГрафикОтпусковСотрудники.ДатаНачала = Отпуск.ДатаНачалаОсновногоОтпуска
ГДЕ
ГрафикОтпусковСотрудники.Ссылка.Проведен
И ГрафикОтпусковСотрудники.Ссылка = &Ссылка
Выясним, почему этот запрос не будет работать в реальной базе данных:
ГрафикОтпусковСотрудники.ДатаНачала = Отпуск.ДатаНачалаОсновногоОтпуска вернет ложь, и фактический отпуск не сопоставится с плановым.Отпуск может содержать как основной, так и дополнительный отпуск, даты которых могут пересекаться или отличаться. Попытка привязаться к полям ДатаНачалаОсновногоОтпуска проигнорирует дополнительные отпуска.Перенос отпуска, плановые даты в самом графике остаются прежними (они не редактируются в проведенном документе), но фактический отпуск проводится уже по новым датам.Для того чтобы построить корректный и надежный запрос, нам необходимо обратиться к архитектуре регистров «1С:ЗУП 3.1». Разработчики фирмы «1С» предусмотрели специальные регистры, в которых фиксируются плановые и фактические показатели движения отпусков. Данные в этих регистрах стандартизированы, что позволяет сопоставлять их без привязки к конкретным реквизитам шапки документов.
Проанализируем ключевые регистры системы:
РегистрСведений.ПлановыеЕжегодныеОтпуска). В этот регистр записываются плановые периоды отдыха при проведении документов График отпусков и Перенос отпуска. Использование этого регистра вместо табличной части документа гарантирует, что мы получим актуальный план с учетом всех последующих переносов и корректировок.РегистрНакопления.ФактическиеОтпуска). Данный регистр является основным источником фактических данных. Сюда делают движения документы Отпуск, Отпуск без сохранения оплаты, а также Увольнение (в части компенсации неиспользованного отпуска). Записи регистра содержат информацию о количестве дней, виде отпуска и, самое главное, о рабочем годе, за который предоставляется этот отпуск.Наиболее методологически верным способом связать плановые и фактические отпуска является сопоставление по рабочему периоду (рабочему году), за который предоставляется отпуск. Каждому запланированному и фактически использованному дню отпуска всегда сопутствуют реквизиты начала и окончания рабочего года.
Рассмотрим, как реализовать такое соединение в запросе по шагам:
ПлановыеЕжегодныеОтпуска или табличной части конкретного графика отпусков. При этом обязательно выбираем поля НачалоРабочегоГода и КонецРабочегоГода.ФактическиеОтпуска. В этом регистре нас интересуют измерения Сотрудник, ВидОтпуска, а также реквизиты рабочего года.Посмотрим на пример практической реализации такого запроса:
ВЫБРАТЬ
ПлановыеЕжегодныеОтпуска.Сотрудник КАК Сотрудник,
ПлановыеЕжегодныеОтпуска.ВидОтпуска КАК ВидОтпуска,
ПлановыеЕжегодныеОтпуска.ДатаНачала КАК ДатаНачалаПлан,
ПлановыеЕжегодныеОтпуска.ДатаОкончания КАК ДатаОкончанияПлан,
ПлановыеЕжегодныеОтпуска.КоличествоДней КАК КоличествоДнейПлан,
ПлановыеЕжегодныеОтпуска.НачалоРабочегоГода КАК НачалоРабочегоГода,
ПлановыеЕжегодныеОтпуска.КонецРабочегоГода КАК КонецРабочегоГода
ПОМЕСТИТЬ ВТ_План
ИЗ
РегистрСведений.ПлановыеЕжегодныеОтпуска КАК ПлановыеЕжегодныеОтпуска
ГДЕ
ПлановыеЕжегодныеОтпуска.ДокументОснование = &ДокументГрафикОтпусков
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ФактическиеОтпуска.Сотрудник КАК Сотрудник,
ФактическиеОтпуска.ВидОтпуска КАК ВидОтпуска,
ФактическиеОтпуска.Регистратор КАК ДокументФакт,
ФактическиеОтпуска.Период КАК ДатаНачалаФакт,
ФактическиеОтпуска.Количество КАК КоличествоДнейФакт,
ФактическиеОтпуска.РабочийГодНачало КАК РабочийГодНачало,
ФактическиеОтпуска.РабочийГодКонец КАК РабочийГодКонец
ПОМЕСТИТЬ ВТ_Факт
ИЗ
РегистрНакопления.ФактическиеОтпуска КАК ФактическиеОтпуска
ГДЕ
ФактическиеОтпуска.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход)
И ФактическиеОтпуска.Сотрудник В (ВЫБРАТЬ РАЗЛИЧНЫЕ ВТ_План.Сотрудник ИЗ ВТ_План)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_План.Сотрудник КАК Сотрудник,
ВТ_План.ВидОтпуска КАК ВидОтпуска,
ВТ_План.ДатаНачалаПлан КАК ДатаНачалаПлан,
ВТ_План.ДатаОкончанияПлан КАК ДатаОкончанияПлан,
ВТ_План.КоличествоДнейПлан КАК КоличествоДнейПлан,
ВТ_План.НачалоРабочегоГода КАК РабочийГодНачало,
ВТ_План.КонецРабочегоГода КАК РабочийГодКонец,
ВТ_Факт.ДокументФакт КАК ДокументФакт,
ВТ_Факт.ДатаНачалаФакт КАК ДатаНачалаФакт,
ВТ_Факт.КоличествоДнейФакт КАК КоличествоДнейФакт
ИЗ
ВТ_План КАК ВТ_План
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Факт КАК ВТ_Факт
ПО ВТ_План.Сотрудник = ВТ_Факт.Сотрудник
И ВТ_План.ВидОтпуска = ВТ_Факт.ВидОтпуска
И ВТ_План.НачалоРабочегоГода = ВТ_Факт.РабочийГодНачало
И ВТ_План.КонецРабочегоГода = ВТ_Факт.РабочийГодКонец
УПОРЯДОЧИТЬ ПО
Сотрудник,
ДатаНачалаПлан
Этот метод является наиболее точным с точки зрения методологии кадрового учета, так как программа жестко контролирует остатки отпусков именно в разрезе рабочих лет сотрудника.
Бывают ситуации, когда в информационной базе по каким-то причинам не ведется детальный учет в разрезе рабочих лет (например, после некорректного переноса данных), либо необходимо сопоставить план и факт исключительно по временной близости событий. В этом случае мы можем применить левое соединение по условию попадания фактической даты начала отпуска в определенный интервал относительно плановой даты начала.
Проанализируем пример такого соединения, где допускается погрешность, например, в 14 или 30 дней:
ВЫБРАТЬ
ГрафикСотрудники.Сотрудник КАК Сотрудник,
ГрафикСотрудники.ДатаНачала КАК ДатаНачалаПлан,
ГрафикСотрудники.ДатаОкончания КАК ДатаОкончанияПлан,
ГрафикСотрудники.КоличествоДней КАК КоличествоДнейПлан,
Отпуск.Ссылка КАК ДокументФакт,
Отпуск.ДатаНачалаОсновногоОтпуска КАК ДатаНачалаФакт,
Отпуск.ДатаОкончанияОсновногоОтпуска КАК ДатаОкончанияФакт
ИЗ
Документ.ГрафикОтпусков.Сотрудники КАК ГрафикСотрудники
ЛЕВОЕ СОЕДИНЕНИЕ Документ.Отпуск КАК Отпуск
ПО ГрафикСотрудники.Сотрудник = Отпуск.Сотрудник
И Отпуск.Проведен
// Задаем интервал: фактический отпуск начался не ранее чем за 15 дней до плана
И Отпуск.ДатаНачалаОсновногоОтпуска >= ДОБАВИТЬКДАТЕ(ГрафикСотрудники.ДатаНачала, ДЕНЬ, -15)
// и не позднее чем через 15 дней после запланированной даты
И Отпуск.ДатаНачалаОсновногоОтпуска <= ДОБАВИТЬКДАТЕ(GraphicSotrudniki.ДатаНачала, ДЕНЬ, 15)
ГДЕ
ГрафикСотрудники.Ссылка = &Ссылка
Важное предупреждение: Данный способ следует использовать с осторожностью. Если сотрудник планировал сходить в отпуск дважды за год на короткие периоды (например, в мае и в июне), то при большой величине интервала фактический майский отпуск может ошибочно привязаться к плановому июньскому периоду.
Для создания надежного и отказоустойчивого алгоритма вывода печатной формы «График отпусков» необходимо учитывать особенности кадрового учета в «1С:ЗУП 3.1»:
Больничный лист. Этот документ автоматически сторнирует (уменьшает) количество дней фактического отпуска. При разработке запроса напрямую к документам вы рискуете вывести исходное (ошибочное) число дней. Обращение к регистру ФактическиеОтпуска вернет скорректированные и актуальные данные с учетом сторно.Компенсация отпуска (или соответствующим флагом в документе Отпуск). В регистре Фактические отпуска эти дни будут списаны, но фактических дат «отдыха» у сотрудника не возникнет. Это необходимо корректно обрабатывать в кодовой части печатной формы.ПлановыеЕжегодныеОтпуска и ФактическиеОтпуска, которые можно использовать в качестве шаблона для своих разработок.Применяя предложенные методики работы с регистрами вместо прямого связывания документов, вы сможете создавать гибкие и безотказные отчеты, которые корректно отобразят плановые и фактические данные по отпускам ваших сотрудников при любых кадровых изменениях.