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