Программное создание и расчет документов начисления зарплаты в конфигурации ЗУП 3.1 и ERP (по подразделениям)
Краткое описание типового механизма расчета зарплаты в конфигурации ЗУП 3.1 и ERP и описание разработанной обработки по программному расчету документов зарплаты в разрезе подразделений. Т.к. формирование документов происходит полностью программно, с минимумов параметров, его можно брать за основу для расчета документов по своим условиям, вплоть до создания по расписанию при необходимости.
- Описание
- Подробнее
Описание
В этой статье расскажу о подходе к программному созданию и расчету документов в ЗУП на примере документа "Начисление зарплаты и взносов". В ERP у нас блок расчета зарплаты тоже есть, поэтому актуально и для этой конфигурации.
Сначала кратко приведу как работает типовой механизм, потом опишу как я это реализовал в обработке.
Сразу уточню, что в моем решении я не использую форму документа, но при этом постарался максимально использовать типовые механизмы. В обработке реализован расчет зарплаты по подразделениям организации.
Как работает типовая логика.
При создании формы документа для табличных частей, использующих показатели, создаются дополнительные колонки Показатель1…ПоказательN и Значение1…ЗначениеN, в которые переносятся данные из табличной части Показали, связанные по реквизиту ИдентификаторСтрокиВидаРасчета.
По команде "Заполнить" запускается расчет в фоновом задании, используется метод "ПодготовитьДанныеДляЗаполнения" из модуля менеджера. Думаю тут не нужно напоминать про существование параметра запуска "РежимОтладки", который позволяет не запускать выполнение в фоне, для удобства отладки.
Результат = ДлительныеОперации.ЗапуститьВыполнениеВФоне(
УникальныйИдентификатор,
"Документы.НачислениеЗарплаты.ПодготовитьДанныеДляЗаполнения",
СтруктураПараметров,
НаименованиеЗадания);
Но в модуле менеджера основной логики нет, тут вызывается функция из общего модуля "Расчет зарплаты расширенный".
ДанныеЗаполнения = РасчетЗарплатыРасширенный.ДанныеДляЗаполненияТаблицДокумента(ОписаниеДокумента, Организация, МесяцНачисления, ДополнительныеПараметры);
И уже в функции "ДанныеДляЗаполненияТаблицДокумента" происходит логика заполнения менеджера расчета, для расчета зарплаты. Ранее я уже описывал данный механизм в статье
Использование менеджера расчета для расчета зарплаты в ЗУП 3.
Если кратко по этой функции, то мы получаем сотрудников для расчета
Сотрудники = СотрудникиДляНачисленияЗарплаты(Организация, Подразделение, МесяцНачисления, ПараметрыСотрудников);
Инициализируем менеджер расчета
МенеджерРасчета = СоздатьМенеджерРасчета(МесяцНачисления, Организация);
Рассчитываем начисления и переносим результаты расчета в данные заполнения, для возврата в документ.
МенеджерРасчета.РассчитатьЗарплату();
ДанныеЗаполнения.Начисления = МенеджерРасчета.Зарплата.Начисления;
После выполнения фонового задания происходит заполнение документа из рассчитанных данных
Если Результат.ЗаданиеВыполнено Тогда
ЗаполнениеПослеВыполненияДлительнойОперации();
КонецЕсли;
Таблицы из формата менеджера расчета преобразуются в формат формы документа
ДанныеДляЗаполненияВДанныеФормы(ДанныеДляЗаполнения, Объект);
Все обработчики переноса находятся общем модуле РасчетЗарплатыРасширенныйФормы
РасчетЗарплатыРасширенныйФормы.РасчетЗарплатыНачисленияВДанныеФормы(ТаблицыНачислений, ДанныеЗаполнения.Начисления, Объект.Организация, Объект.МесяцНачисления, ПозицииВставки, Объект.РежимДоначисления);
В событии "ПередЗаписьюНаСервере" происходит перенос показателей из табличных частей в табличную часть "Показатели"
ЗарплатаКадрыРасширенный.ВводНачисленийРеквизитВДанные(ЭтаФорма, ТекущийОбъект, ОписанияТаблиц, 2);
Вроде бы все просто, бери, копируй код в обработку, пиши свое решение. Сложность в том, что все механизмы переноса данных менеджера расчета в данные формы рассчитаны на форму. Все методы находятся в общем модуле РасчетЗарплатыРасширенныйФормы. В коде есть такие методы как Строка.Свойство(), что мы можем применить только к ДанныеФормыКоллекция.
Раньше я уже реализовывал подобную задачу, переписав методы РасчетЗарплатыРасширенныйФормы без использования специфичных свойств. В этот раз для статьи, как мне кажется я нашел более изящное решение.
Собственно интерфейс обработки
Я максимально упростил условия. Т.е. в шапке у нас только месяц расчета организация и ответственный.
РежимНачисления — окончательный расчет, порядок выплаты — зарплата.
В табличной части подразделения организации, не имеющие подчиненных подразделений. По каждой строке рассчитывается отдельный документ. Это бывает удобно в крупных организациях, когда численность сотрудников не позволяет их рассчитать в одном документе, а количество подразделений сильно усложняет время расчета.
Расчет идентичный тому, что если вы создадите документ, укажите необходимы реквизиты и нажмете кнопку "Заполнить".
Расчет реализован через длительные операции, т.е. через фоновые задания. Естественно, т.к. это внешняя обработка, она должна быть подключена в дополнительные отчеты и обработки. При открытии напрямую, будет работать без фонового задания.
Выполнение команды через длительные операции отображением прогресса работы.
Возврат ДлительныеОперации.ВыполнитьПроцедуру(
ПараметрыВыполнения,
"ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки",
ПараметрыЗадания,
АдресХранилища);
Весь расчет, создание и заполнение документа проходит в модуле обработки. После выполнения на клиенте обновляется таблица подразделений с документами. Никаких заполнений документов не происходит.
В модуле мы обходим в цикле выбранные подразделения и для каждого делаем расчет, в процессе сообщая прогресс выполнения
Процедура РасчитатьЗарплату(Параметры, АдресРезультата) Экспорт
Подразделения = Параметры.Подразделения;
ПорядковыйНомер = 1;
КоличествоПодразделений = Подразделения.Количество();
Для Каждого ПараметрыРасчета Из Подразделения Цикл
Процент = Окр(ПорядковыйНомер / КоличествоПодразделений * 100, 0);
ДлительныеОперации.СообщитьПрогресс(Процент,
СтрШаблон(
НСтр("ru = 'Расчет подразделения %1 (%2 из %3)'"),
ПараметрыРасчета.Подразделение,
ПорядковыйНомер,
КоличествоПодразделений));
РасчитатьДокумент(Параметры, ПараметрыРасчета);
ПорядковыйНомер = ПорядковыйНомер + 1;
КонецЦикла;
КонецПроцедуры
В процедуре РасчитатьДокумент создаем или получаем объект документа
Если ЗначениеЗаполнено(ПараметрыРасчета.Документ) Тогда
ДокументОбъект = ПараметрыРасчета.Документ.ПолучитьОбъект();
Иначе
ДокументОбъект = Документы.НачислениеЗарплаты.СоздатьДокумент();
КонецЕсли;
Как уже писал, использую максимально типовые механизмы, поэтому расчет проходит так же через типовой метод
Документы.НачислениеЗарплаты.ПодготовитьДанныеДляЗаполнения(ПараметрыВыполнения, АдресХранилища);
СтруктураДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
Ну и заполнение документа
Если СтруктураДанных.Свойство("ДанныеДляЗаполненияТаблицДокумента", ДанныеДляЗаполнения) Тогда
// Заполнение табличных частей и вторичных данных коллекций, которые с ней связаны.
ОчиститьТаблицыДокументаНаСервере(ДокументОбъект);
//Эмуляция формы для удобства работы с типовыми механизмами
//Для использования максимально типового функциола смоделируем в объекте данные формы документа
ЭтаФорма = ОбщегоНазначенияКлиентСервер.СкопироватьСтруктуру(ПараметрыВыполнения);
ЭтаФорма.Вставить("ВременныйОбъект", ДанныеДокументаВОбъект(ДокументОбъект));
//Данные процедура полностью идентичная аналогичной процедуре формы документа НачислениеЗарплаты
ДанныеДляЗаполненияВДанныеОбъекта(ДанныеДляЗаполнения, ЭтаФорма.ВременныйОбъект);
//Окончательно перенесем данные объекта в данные документа
ДанныеОбъектаВДанныеДокумента(ЭтаФорма, ДокументОбъект, ПараметрыВыполнения);
С учетом того, что мы в том числе перезаполняем документ, то чистим все таблицы документа
Для Каждого ТабличнаяЧасть Из Метаданные.Документы.НачислениеЗарплаты.ТабличныеЧасти Цикл
ДокументОбъект[ТабличнаяЧасть.Имя].Очистить();
КонецЦикла;
Будем считать что в ТЧ ДополнительныеРеквизиты у нас пусто 🙂
Для переноса данных менеджера расчета в данные "формы", использую копию процедуры формы документа "ДанныеДляЗаполненияВДанныеОбъекта".
Так же в модуль перенесены и адаптированы функции модуля формы по описанию документа и таблиц начислений (льготы, взносы, налоги, удержания в зависимости от настроек программы).
Т.к. использую типовые механизмы переноса, но при этом не используя форму, то например в общем модуле ЗарплатаКадрыРасширенный.ВводНачисленийРеквизитВДанныеТаблицыРасчета пришлось обходить вот такое условие
Если СтрокаКоллекции.Свойство("ДокументОснование") Тогда
СтруктураДокументОснование.ДокументОснование = СтрокаКоллекции.ДокументОснование;
ЗаполнитьЗначенияСвойств(НоваяСтрокаПоказателя, СтруктураДокументОснование);
КонецЕсли;
Для этого вместо таблиц значений использовал массивы структур
Форма.Объект.Вставить(Элемент.Ключ, ОбщегоНазначения.ТаблицаЗначенийВМассив(Элемент.Значение));
Тут форма — это структура. Сделано для удобства и "эмуляции" формы.
Показатели в таблицы добавляю програмно
МаксимальноеКоличествоПоказателей = ЗарплатаКадрыРасширенныйПовтИсп.МаксимальноеКоличествоОтображаемыхПоказателей();
Для Каждого ТабличнаяЧасть Из Метаданные.Документы.НачислениеЗарплаты.ТабличныеЧасти Цикл
Объект.Вставить(ТабличнаяЧасть.Имя, ДокументОбъект[ТабличнаяЧасть.Имя].Выгрузить());
Для НомерПоказателя = 1 По МаксимальноеКоличествоПоказателей Цикл
Объект[ТабличнаяЧасть.Имя].Колонки.Добавить("Показатель" + НомерПоказателя, Новый ОписаниеТипов("СправочникСсылка.ПоказателиРасчетаЗарплаты"));
Объект[ТабличнаяЧасть.Имя].Колонки.Добавить("Значение" + НомерПоказателя, Метаданные.ОпределяемыеТипы.ЗначениеПоказателяРасчетаЗарплаты.Тип);
КонецЦикла;
КонецЦикла;
Записываем документ с чисткой перерасчетов.
ДокументОбъект.ДополнительныеСвойства.Вставить("УдалитьПерерасчетыТекущегоПериода", Истина);
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
ОбщегоНазначения.СообщитьПользователю(СтрШаблон(НСтр("ru = 'Не удалось провести расчетный документ %1 по причине:
|%2'"), ДокументОбъект.Ссылка, КраткоеПредставлениеОшибки(ОписаниеОшибки())));
КонецПопытки;
На будущее добавлено сохранения вида операции, если планируется разделять документы по видам операций.
Если ОбщегоНазначения.ПодсистемаСуществует("ЗарплатаКадрыПриложения.ОперацииРасчетаЗарплаты") И ЗначениеЗаполнено(ДокументОбъект.Ссылка) Тогда
Модуль = ОбщегоНазначения.ОбщийМодуль("ОперацииРасчетаЗарплаты");
Модуль.ЗаписатьВидОперацииДокумента(ДокументОбъект.Ссылка, ПараметрыВыполнения.ВидОперации);
КонецЕсли;
Разработано в конфигурации 1С:Предприятие 8.3 (8.3.20.1613)
Код актуален для конфигурации Зарплата и управление персоналом КОРП, редакция 3.1 (3.1.20.71). Дополнительно протестировал работу в 1С:ERP Управление предприятием 2 (2.5.7.201) (В демо версии подразделения для теста есть в организации "Деловой союз").
В конфигурации очень много своей специфики. Например по нумерации ИдентификаторСтрокиВидаРасчета в зависимости от документа и табличной части. Или например менеджер расчета вычеты возвращает в ТЧ НДФЛ, которые потом переносятся в отдельную табличную часть. Показатели, которые в менеджере расчета хранятся для каждой строки в отдельной таблице значений, а в документа одна табличная часть Показатели для всех ТЧ, связанных по идентификатору.
К чему это я. Результаты работы данный обработки я сравнивал через выгрузку документа через обработку "Выгрузка загрузка данных в XML" и затем сравнивая результаты расчета через обработку и вручную.
Весь код показать не могу, слишком много для статьи. Мог упустить что-нибудь важное в статье, отвечу в комментариях. Если что-то конкретное, то приведу пример кода. Обработка писалась с нуля исключительно для данной статьи.
Для данной обработки функциональная опция "Выполнять расчет зарплаты по подразделениям" должна быть включена. Это можно сделать в настройках сняв галку на пункте "Расчет и выплата зарплаты выполняется по организации в целом".