При автоматизации кадрового учета или переносе данных из сторонних систем программисты 1С часто сталкиваются с задачей программного создания документов. Один из самых популярных и одновременно сложных в реализации документов — это Прием на работу в конфигурации «Зарплата и управление персоналом 3.1» (а также в «1С:ERP» и «1С:Комплексная автоматизация»).
Разработчики нередко замечают странное поведение платформы: документ создается, сотрудник проводится, но табличная часть начислений ведет себя некорректно. Оклад или тарифная ставка упорно сбрасываются в 0, а в истории изменения начислений сотрудника внезапно образуются двойные записи. В этой статье мы подробно разберем, почему возникают эти ошибки, проанализируем внутреннее устройство регистров ЗУП 3.1 и напишем правильный, отказоустойчивый код для программного заполнения документа.
Давайте проанализируем архитектуру хранения данных в ЗУП 3.1. В отличие от старых версий программы (например, ЗУП 2.5), здесь колонка Размер в табличной части Начисления является расчетной величиной. Напрямую записать туда числовое значение оклада недостаточно. Эта колонка жестко связана с показателями расчета зарплаты, которые используются в математической формуле конкретного начисления.
Если мы программно добавим строку только в табличную часть Начисления, но оставим пустой подчиненную табличную часть Показатели, произойдет следующее: при записи или проведении документа система запустит встроенные механизмы проверки. Не найдя исходных данных (показателей) для расчета оклада, алгоритм платформы автоматически пересчитает размер начисления и сбросит его в 0.
Для того чтобы значение сохранялось, нам необходимо параллельно заполнять две табличные части документа:
Связующим звеном между строками этих двух табличных частей выступает реквизит ИдентификаторСтрокиВидаРасчета. Это целочисленный индекс, который должен быть одинаковым для начисления и всех соответствующих ему показателей.
Обратим внимание на то, как исходно завершалась процедура записи документа в коде:
ПриёмНаРаботу.Записать(РежимЗаписиДокумента.Запись);
ПриёмНаРаботу.Записать(РежимЗаписиДокумента.Проведение);
Это классическая ошибка начинающих разработчиков. Метод Записать(РежимЗаписиДокумента.Проведение) платформы 1С:Предприятие автоматически производит запись объекта перед его проведением в рамках единой транзакции. Вызывая сначала метод обычной записи, а затем метод проведения, мы заставляем систему отрабатывать транзакцию дважды.
В ЗУП 3.1 на события записи и проведения завязано огромное количество подписок на события. При первой записи система регистрирует плановые начисления и создает записи в периодических регистрах сведений (таких как «Плановые начисления» или «Значения периодических показателей расчета зарплаты (для сотрудников)»). При последующем проведении этот цикл запускается снова, что приводит к дублированию или некорректному вытеснению записей в истории сотрудника. Нам необходимо оставить только одну инструкцию проведения.
Когда кадровик заполняет документ «Прием на работу» вручную на форме, расчет районного коэффициента (РК), северных надбавок и общего фонда оплаты труда (ФОТ) происходит интерактивно с помощью клиент-серверных обработчиков событий формы. При программном создании объекта на сервере эти визуальные события формы не срабатывают.
Чтобы документ провелся корректно и сформировал правильный ФОТ, нам необходимо принудительно запустить механизмы расчета зарплаты, встроенные в конфигурацию. Для этого используются процедуры специализированных общих модулей, таких как РасчетЗарплатыРасширенный.
Рассмотрим подробнее правильный алгоритм программного создания документа приема на работу с заполнением всех ключевых табличных частей.
Сначала мы инициализируем новый документ и заполняем его базовые реквизиты. Важно корректно указать сотрудника, организацию, подразделение, должность и дату приема.
ПриёмНаРаботу = Документы.ПриемНаРаботу.СоздатьДокумент();
ПриёмНаРаботу.Дата = ТекущаяДата();
ПриёмНаРаботу.Организация = ВыбраннаяОрганизация;
ПриёмНаРаботу.Сотрудник = СсылкаНаСотрудника;
ПриёмНаРаботу.ФизическоеЛицо = СсылкаНаСотрудника.ФизическоеЛицо; // Обязательно для кадрового учета
// Заполнение кадровых данных
ПриёмНаРаботу.ДатаПриема = ДатаПриемаНаРаботу;
ПриёмНаРаботу.Подразделение = ВыбранноеПодразделение;
ПриёмНаРаботу.Должность = ВыбраннаяДолжность;
ПриёмНаРаботу.КоличествоСтавок = 1;
ПриёмНаРаботу.ГрафикРаботы = ВыбранныйГрафикРаботы;
ПриёмНаРаботу.ВидЗанятости = Перечисления.ВидыЗанятости.ОсновноеМестоРаботы;
Теперь добавим строку в табличную часть начислений. Присвоим строке уникальный идентификатор (например, число 1).
НовоеНачисление = ПриёмНаРаботу.Начисления.Добавить();
НовоеНачисление.Начисление = ПланыВидовРасчета.Начисления.НайтиПоНаименованию("Оклад");
НовоеНачисление.ИдентификаторСтрокиВидаРасчета = 1;
НовоеНачисление.Размер = СуммаОклада; // Продублируем значение для наглядности
Это самый важный шаг. Мы должны связать наше начисление с конкретным показателем («Тарифная ставка месячная» или «Оклад»), используя тот же идентификатор строки.
НовыйПоказатель = ПриёмНаРаботу.Показатели.Добавить();
НовыйПоказатель.ИдентификаторСтрокиВидаРасчета = 1; // Связь с ТЧ "Начисления"
НовыйПоказатель.Показатель = Справочники.ПоказателиРасчетаЗарплаты.НайтиПоНаименованию("Оклад");
НовыйПоказатель.Значение = СуммаОклада;
Чтобы система зафиксировала наши изменения и рассчитала зависимые показатели (включая ФОТ), взведем соответствующий флаг и вызовем процедуру расчета:
ПриёмНаРаботу.НачисленияУтверждены = Истина;
// Вызываем типовой пересчет документа для корректного формирования ФОТ
ПараметрыПересчета = РасчетЗарплатыРасширенный.ПараметрыРедактированияОбъекта(ПриёмНаРаботу);
РасчетЗарплатыРасширенный.ПриРедактированииНачисленийПоказателей(ПриёмНаРаботу, ПараметрыПересчета);
На завершающем этапе мы оборачиваем транзакцию в попытку-исключение и выполняем строго одну операцию проведения.
Попытка
ПриёмНаРаботу.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Документ успешно создан и проведен!");
Исключение
ВызватьИсключение "Ошибка проведения документа: " + ОписаниеОшибки();
КонецПопытки;
Объединим все наши шаги в готовую к использованию экспортную процедуру, которую можно разместить в общем модуле или модуле менеджера вашей обработки загрузки данных.
Процедура ПрограммноСоздатьПриемНаРаботу(ДанныеПриема) Экспорт
ДокументОбъект = Документы.ПриемНаРаботу.СоздатьДокумент();
ДокументОбъект.Дата = ТекущаяДата();
// Заполнение основных реквизитов шапки
ДокументОбъект.Организация = ДанныеПриема.Организация;
ДокументОбъект.Сотрудник = ДанныеПриема.Сотрудник;
ДокументОбъект.ФизическоеЛицо = ДанныеПриема.Сотрудник.ФизическоеЛицо;
ДокументОбъект.Подразделение = ДанныеПриема.Подразделение;
ДокументОбъект.Должность = ДанныеПриема.Должность;
ДокументОбъект.ДатаПриема = ДанныеПриема.ДатаПриема;
ДокументОбъект.ГрафикРаботы = ДанныеПриема.ГрафикРаботы;
ДокументОбъект.КоличествоСтавок = ДанныеПриема.КоличествоСтавок;
ДокументОбъект.ВидЗанятости = ДанныеПриема.ВидЗанятости;
// Настройки аванса и трудовой книжки
ДокументОбъект.СпособРасчетаАванса = Перечисления.СпособыРасчетаАванса.ФиксированнойСуммой;
ДокументОбъект.Аванс = ДанныеПриема.СуммаАванса;
ДокументОбъект.ОтразитьВТрудовойКнижке = Истина;
// Заполнение начисления (Оклад)
СтрокаНачисления = ДокументОбъект.Начисления.Добавить();
СтрокаНачисления.Начисление = ДанныеПриема.ВидНачисления; // Ссылка на ПВР.Начисления
СтрокаНачисления.ИдентификаторСтрокиВидаРасчета = 1;
СтрокаНачисления.Размер = ДанныеПриема.РазмерНачисления;
// Заполнение показателя расчета
СтрокаПоказателя = ДокументОбъект.Показатели.Добавить();
СтрокаПоказателя.ИдентификаторСтрокиВидаРасчета = 1;
СтрокаПоказателя.Показатель = ДанныеПриема.Показатель; // Ссылка на Справочник.ПоказателиРасчетаЗарплаты
СтрокаПоказателя.Значение = ДанныеПриема.РазмерНачисления;
ДокументОбъект.НачисленияУтверждены = Истина;
// Автоматический пересчет ФОТ и зависимых начислений типовыми средствами ЗУП
ПараметрыРедактирования = РасчетЗарплатыРасширенный.ПараметрыРедактированияОбъекта(ДокументОбъект);
РасчетЗарплатыРасширенный.ПриРедактированииНачисленийПоказателей(ДокументОбъект, ПараметрыРедактирования);
// Заполнение подписантов по умолчанию
Подписанты = Новый Структура("Организация, Руководитель", ДокументОбъект.Организация);
ЗарплатаКадры.ПолучитьЗначенияПоУмолчанию(Подписанты, ДокументОбъект.Дата);
ДокументОбъект.Руководитель = Подписанты.Руководитель;
// Запись документа в режиме проведения
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ЗаписьЖурналаРегистрации("ПрограммноеСозданиеПриема", УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось провести документ для сотрудника " + ДокументОбъект.Сотрудник + ". Ошибка: " + ОписаниеОшибки());
ВызватьИсключение "Ошибка при проведении документа: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Если в вашей информационной базе ведется история штатного расписания, вы можете существенно упростить себе жизнь. Вместо того чтобы вручную заполнять начисления и показатели, можно использовать встроенную функцию автозаполнения условий договора по позиции штатного расписания.
Для этого достаточно заполнить реквизит ДолжностьПоШтатномуРасписанию и вызвать процедуру общего модуля:
ДокументОбъект.ДолжностьПоШтатномуРасписанию = СсылкаНаПозициюШтатногоРасписания;
// Заполняем начисления и показатели на основе настроек штатного расписания
КадровыйУчет.ЗаполнитьУсловияДоговораПоШтатномуРасписанию(ДокументОбъект);
Этот метод самостоятельно добавит все необходимые виды расчетов (включая оклад, районный коэффициент, надбавки за вредность и т.д.) в табличные части и заполнит связанные показатели. Вам останется лишь скорректировать значения при необходимости и провести документ.
Применяя данные подходы, вы навсегда избавитесь от проблемы "нулевых" окладов в документах приема и обеспечите абсолютную чистоту регистров истории сотрудников.