ЗУП для начинающих программистов
Конфигурация ЗУП8 (Зарплата и Управление Персоналом) – одна из самых сложных типовых конфигураций для понимания начинающими программистами.
Новичку сложно самому разобраться в структуре и взаимосвязях процедур и функций,предназначенных для расчета, а еще сложнее понять почему все сделано именно так.
Часто неопытный программист, «проковырявшись» в общем модуле «ПроведениеРасчетов» несколько часов, наконец, с радостью находит место в процедуре «РассчитатьЗаписьРегистраРасчета» (это самая простая для понимания процедура в модуле), где рассчитывается нужный ему способ расчета и «тулит» туда запросы и сложные обработки данных, что бы изменить или добавить что-то в алгоритме.
Для таких программистов и предназначена эта статья. В статье рассматриваются только самые сложные механизмы конфигурации, связанные с проведением расчетов. Описание составлено с использованием украинской версии конфигурации редакции 2.1, но подойдет и для российской, так как концепция конфигураций одинаковая. Также справедливо все написанное для соответствующих подсистем УПП редакции 1.2 для Украины.
НАЧАЛО
При разработке типовой конфигурации ЗУП (Зарплата и Управление Персоналом) для платформы 8, компания 1С преследовала две важные цели:
1. Скорость (производительность) выполнения расчетов.
2. Возможность ручной корректировки результатов расчета.
Именно эти проблемы больше всего беспокоили пользователей и программистов в конфигурации ЗиК (Зарплата и кадры) для платформы версии 7.7. Побочным неудобством решения этих проблем в конфигурации ЗУП является сложность понимания основных механизмов конфигурации и как следствие сложность доработки.
Регистры расчета
Для проведения расчетов конфигурация ЗУП использует регистры расчета. Регистры расчета обладают следующими уникальными свойствами, которые не присущи другим типам регистров:
1. Конкуренция по периоду действия.
2. Связь с графиком.
3. Возможность получения базы.
4. Регистрация необходимости перерасчетов.
Эти уникальные свойства регистров расчета, а также связанные с ними виртуальные таблицы подробно описаны в книгах М. Радченко «Практическое пособие разработчика» (главы 9 и 10) и «Профессиональная разработка в системе 1С:Предприятие 8» (глава 10 «Реализация сложных периодических расчетов»).
Также на эту тему есть хорошие видеокурсы…
Типовая структура документа расчета
В большинстве случаев структура табличных частей документов совпадает со структурой соответствующих регистров расчета.
Большинство модулей расчетных документов реализовано однотипно. В модуле расчетного документа обычно содержатся процедуры:
1. ЗаполнитьНачисления (или аналогичная, предназначенная для заполнения табличной части документа)
2. РассчитатьНачисления (или аналогичная, предназначенная для расчета табличной части документа)
Структура процедуры «РассчитатьНачисления» модуля документа:
1. Выполняется запрос по табличной части документа:
ВыборкаПоНачислениям = СформироватьЗапросПоНачислениям().Выбрать();
2. Создаются рабочие наборы записей необходимого(ых) регистра(ов) расчета:
НаборОсновныеНачисления = РегистрыРасчета.ОсновныеНачисленияРаботниковОрганизаций.СоздатьНаборЗаписей();
3. В цикле обходится результат запроса. Каждая строка проверяется на правильность заполнения и добавляется в наборы (созданные на шаге 1):
Пока ВыборкаПоНачислениям.Следующий() Цикл
ПроверитьЗаполнениеСтрокиНачисления(ВыборкаПоШапкеДокумента,ВыборкаПоНачислениям, Отказ);
Если НЕ Отказ Тогда
// Заполним записи в наборах записей регистров
ДобавитьСтрокуОсновныхНачислений(ВыборкаПоНачислениям, НаборОсновныеНачисления);
МассивИндексыСтрокНачисления.Добавить(ВыборкаПоНачислениям.НомерСтроки-1);
КонецЕсли;
КонецЦикла;
«МассивИндексыСтрокНачисления» используется если необходимо рассчитать не все строки документа, а только некоторые (например, только по одному работнику). «МассивИндексыСтрокНачисления» используется в общем модуле «ПроведениеРасчетов» в процедуре «РассчитатьЗаписиНабора».
4. Наборы записываетсяются в регистры:
НаборОсновныеНачисления.Записать();
5. Если табличная часть документа некорректно заполнена и какая-то строка не прошла проверку на заполнение, то наборы очищаются и пустые наборы записываются в регистры. Далее расчет не производится:
Если Отказ Тогда
// Если есть какие-то проблемы - удаляем движения (тут нет транзакции)
// Удаляем движения
НаборОсновныеНачисления.Очистить();
НаборОсновныеНачисления.Записать();
Возврат;
КонецЕсли;
6. Если все хорошо, то в наборы добавляются записи сторно, для чего используется механизм дополнений регистра расчета:
ТаблицаСторноЗаписей = НаборОсновныеНачисления.ПолучитьДополнение();
...
Для каждого СтрокаСторно Из ТаблицаСторноЗаписей Цикл
// Заполним записи в наборе записей регистра
Движение = НаборОсновныеНачисления.Добавить();
ЗаполнитьЗначенияСвойств(Движение, СтрокаСторно);
Движение.Сторно = Истина;
...
КонецЦикла;
Функция «ПолучитьДополнение» набора записей регистра расчета получает дополнительные данные, позволяющие выполнить сторнирование записей прошлых периодов регистрации при вводе текущего набора записей. Подробнее об этой функции можно почитать в синтаксис-помошнике.
7. Наборы опять записываются в регистры
НаборОсновныеНачисления.Записать();
8. Вызывается экспортная процедура «РассчитатьЗаписиРегистраРасчета» общего модуля «Проведение расчетов»:
ПроведениеРасчетов.РассчитатьЗаписиРегистраРасчета("ОсновныеНачисленияРаботниковОрганизаций",
НаборОсновныеНачисления,
Начисления,
МассивИндексыСтрокНачисления,
Сотрудники,
КомментироватьРасчет);
В процедуре выполняется расчет записей набора регистра расчета по определенным алгоритмам. Результатом расчета являются заполненные ресурсы «Результат» и «ОплаченоДнейЧасов» в записях набора. Также процедура обновляет результаты расчета в табличной части документа. Подробно общий модуль «Проведение расчетов» будет описан далее.
9. Наборы очищаются и пустыми записываются в регистры:
НаборОсновныеНачисления.Очистить();
НаборОсновныеНачисления.Записать();
10. Записывается и сам документ:
Записать();
Таким образом в табличной части документа появляются рассчитанные данные.
При проведении документа расчета его табличная часть, соответствующая регистру расчета, просто записывается в регистр расчета и никакие вычисления уже не производятся.
Здесьу начинающего программиста может возникнуть два вопроса:
1. Почему нельзя выполнить расчеты при проведении документа?
2. Как и когда при расчете используются уникальные свойства регистров расчета?
Ответ на первый вопрос:
Вспомним вторую цель конфигурации «Возможность ручной корректировки результатов расчета». Именно после расчета и до проведения документа пользователь может корректировать расчеты. При проведении в регистры расчета запишутся уже исправленные данные. Кстати откорректированные строки помечаются специальным признаком и при последующих расчетах не изменяются.
Ответ на второй вопрос:
Уникальные свойства регистров расчета становятся доступными после записи наборов в регистры и используются в процедуре «РассчитатьЗаписиРегистраРасчета» общего модуля «Проведение расчетов».
Необычным для понимания является то, что основной функционал документа реализован в обработчике кнопки «Рассчитать», а не в обработчике «ОбработкаПроведения» как в большинстве документов конфигураций оперативного учета или бухгалтерии.
Виды расчетов
Совместно с регистрами расчета в конфигурации используются планы видов расчета. Планы видов расчета представляют собой списки всех начислений и удержаний, расчет которых возможен в конфигурации. В планах видов расчета описываются специфические свойства начислений и удержаний (видов расчета), используемых при расчете. Самыми важными свойствами видов расчета в конфигурации являются:
1. Категория расчета
2. Способ расчета
Свойство «Категория расчета» определяет последовательность расчета записей. Вначале будут рассчитаны виды расчета с категорией «Первичное», затем «Зависимое первого уровня», затем «Зависимое второго уровня» и т.д. Для чего это надо рассмотрим на примере.
Допустим, мы используем начисление «Премия», которое рассчитывается как процент от начисленного по окладу. Очевидно, что при расчете премии уже должно быть рассчитано начисление по окладу. Поэтому виду расчета «Начислено по окладу» необходимо установить категорию «Первичное», а виду расчета «Премия» необходимо установить категорию «Зависимое первого уровня».
Свойство «Способ расчета» определяет алгоритм, который будет применяться при расчете данного вида расчета. Так как способы расчета задаются в перечислении, то список алгоритмов конфигурации ограничен, и пользователь не имеет возможности добавить новый алгоритм. Точнее, пользователь имеет возможность использовать универсальные способы расчета, в которых можно задавать различные формулы, но многие не используют эту возможность, а просят программиста добавить новый способ расчета.
Еще важно понять, что категория и способ расчета не являются встроенными (предопределенными) реквизитами плана видов расчета, поддерживаемые на уровне платформы, поэтому их обработка выполняется средствами прикладного решения - конфигурации.
Общий модуль «ПроведениеРасчетов»
Процедура модуля «РассчитатьЗаписиРегистраРасчета».
Процедура «РассчитатьЗаписиРегистраРасчета» является основной экспортной процедурой общего модуля «ПроведениеРасчетов» и предназначена для расчета набора записей определенного регистра расчета. Результатами расчета являются ВСЕ ресурсы регистра расчета.
Процедура вызывается из модулей всех расчетных документов при нажатии на кнопку «Рассчитать». Перед вызовом в документах формируется и записывается набор записей, который будет рассчитываться. Основными параметрами вызова процедуры являются имя регистра («ИмяРегистра» типа «Строка»), собственно сам набор записей («НаборЗаписейРегистра» типа «НаборЗаписейРегистраРасчета»)
Процедура РассчитатьЗаписиРегистраРасчета(ИмяРегистра,
НаборЗаписейРегистра,
ТабличнаяЧастьДокумента = Неопределено,
МассивИндексыСтрокТабличнойЧасти = Неопределено,
Сотрудники = Неопределено,
КомментироватьРасчет = Ложь,
СотрудникиРасчетОтОбратного = Неопределено) Экспорт
Если в процедуру будет передан параметр «ТабличнаяЧастьДокумента», то результаты расчета будут помещены в табличную часть документа.
Описание работы процедуры:
1. Определяется список видов расчета, которые необходимо рассчитать:
ТаблицаВидовРасчета = НаборЗаписейРегистра.Выгрузить();
ТаблицаВидовРасчета.Свернуть("ВидРасчета");
ВидыРасчетов = ТаблицаВидовРасчета.ВыгрузитьКолонку("ВидРасчета");
2. Выполняется запрос по плану видов расчета. Выбираются различные категории, способ и показатели расчетов. Выборка ограничивается видами расчета из списка. Результат группируется и упорядочивается по категории расчета.
3. Далее для каждой категории составляется массивы способов и показателей расчета:
Пока ВыборкаКатегорий.Следующий() Цикл
МассивСпособовРасчета = Новый Массив; // Массив способов расчета текущий категории
МассивПоказателей = Новый Массив; // Массив видов показателей схем мотивации
...
ВложеннаяВыборка = ВыборкаКатегорий.Выбрать();
Пока ВложеннаяВыборка.Следующий() Цикл
МассивСпособовРасчета.Добавить(ВложеннаяВыборка.СпособРасчета);
Если ИмяПВР = "ОсновныеНачисленияОрганизаций" Тогда
Если ВложеннаяВыборка.Показатель1 <> Null Тогда
МассивПоказателей.Добавить(ВложеннаяВыборка.Показатель1);
КонецЕсли;
...
КонецЕсли;
...
КонецЦикла;
4. С помощью функции «ПолучитьСтруктуруНеобходимыхДанных» определяются какие данные будут необходимы для расчета:
НеобходимыеДанные = ПолучитьСтруктуруНеобходимыхДанных(МассивСпособовРасчета, МассивПоказателей);
Параметрами вызова являются массивы способов и показателей расчета. Функция возвращает структуру флагов необходимых данных, значение каждого флага (элемента) которой может принимать булево значение. «Истина» будет установлена только для тех элементов структуры, которые будут необходимы для расчета. Функция «ПолучитьСтруктуруНеобходимыхДанных» будет подробно описана далее.
5. Рассчитывается фактический период действия записей набора:
НаборЗаписейРегистра.Записать(Истина, ТолькоЗапись);
При выполнении записи переменной «ТолькоЗапись» присвоено значение «Ложь» (до начала цикла по категориям), поэтому выполняется запись с расчетом фактического периода действия (см. второй параметр процедуры в синтаксис-помошнике).
После первой записи переменной «ТолькоЗапись» присваивается значение «Истина», так как расcчитывать период действия нужно только один раз (при первой записи, когда рассчитывается первая категория).
ТолькоЗапись = Истина;
При расчете следующих категорий набор будет записан без расчета фактического периода действия, что значительно повышает производительность выполнения расчетов.
6. С помощью функции «ПолучитьДанныеДляРасчета» получаем ВСЕ, необходимые для расчета текущей категории, данные:
ИсходныеДанные = ПолучитьДанныеДляРасчета(ИмяРегистра, ВыборкаКатегорий.КатегорияРасчета, НеобходимыеДанные, Регистратор, Организация, ПериодРегистрации, , Сотрудники, КомментироватьРасчет);
Структура «НеобходимыеДанные» передается в функцию через одноименный параметр. Функция возвращает выборку из результата запроса, полями которого являются исходные данные для ВСЕХ расчетов текущей категории. Функция «ПолучитьДанныеДляРасчета» является самой сложной функцией общего модуля «ПроведениеРасчетов» и будет подробно описана далее.
7. С помощью процедуры «РассчитатьЗаписиНабора» производится расчет записей набора для которых получены исходные данные:
РассчитатьЗаписиНабора(ИсходныеДанные, НаборЗаписейРегистра, НеобходимыеДанные, ТабличнаяЧастьДокумента, МассивИндексыСтрокТабличнойЧасти, КомментироватьРасчет,, СотрудникиРасчетОтОбратного);
Через параметры в процедуру передаются исходные данные для расчетов ( структура «ИсходныеДанные»), определенные ранее с помощью функции «ПолучитьДанныеДляРасчета» и набор записей регистра. В этой же процедуре происходит заполнение табличной части документа, если передан параметр «ТабличнаяЧастьДокумента».
8. После расчета набор записей регистра опять записывается, но уже без расчета фактического периода действия:
НаборЗаписейРегистра.Записать(Истина, ТолькоЗапись);
9. Далее выполняются специфические действия только для регистра расчета «ВзносыВФонды»(для украинских конфигураций):
Если ИмяРегистра = "ВзносыВФонды" Тогда
ИсходныеДанныеСторно = ПолучитьДанныеДляРасчетаСторноВзносовВФонды( Регистратор, ЭтоВзносы, Сотрудники, КомментироватьРасчет);
РассчитатьЗаписиНабораСторноВзносовВФонды(ИсходныеДанныеСторно, НаборЗаписейРегистра, ТабличнаяЧастьДокумента, МассивИндексыСтрокТабличнойЧасти, КомментироватьРасчет);
КонецЕсли;
Замечания по доработке:
Обычно эта процедура не требует доработок.
Функция «ПолучитьСтруктуруНеобходимыхДанных»
Функция возвращает структуру флагов необходимых для расчета данных, значение каждого флага(элемента) которой может принимать булево значение. Функция анализирует массив способов и показателей расчета и устанавливает значение «ИСТИНА» только для тех элементов структуры, которые необходимы для расчета. Текст функции можно разделить на три части:
1. Создание структуры необходимых данных со всеми флагами, установленными в значение «ЛОЖЬ»:
НеобходимыеДанные = Новый Структура;
НеобходимыеДанные.Вставить("База", Ложь);
НеобходимыеДанные.Вставить("БазаПоНазначению", Ложь);
НеобходимыеДанные.Вставить("БазаПоНоменклатурнойГруппе", Ложь);
НеобходимыеДанные.Вставить("БазаУдержаний", Ложь);
...
2. Обход массива способов расчета и установки флагов, необходимых данных для каждого способа:
Для Каждого СпособРасчета Из МассивСпособРасчета Цикл
Если СпособРасчета = Перечисления.СпособыРасчетаОплатыТруда.ГосударственноеПособие Тогда
НеобходимыеДанные.ГосударственноеПособие = Истина;
НеобходимыеДанные.ГосударственноеПособиеУчитыватьВремя = Истина;
ИначеЕсли СпособРасчета = Перечисления.СпособыРасчетаОплатыТруда.ПоМесячнойТарифнойСтавкеПоЧасам Тогда
НеобходимыеДанные.ОтработаноВремени = Истина;
НеобходимыеДанные.НормаВремени = Истина;
НеобходимыеДанные.НормаВремениЗаМесяц = Истина;
...
КонецЕсли;
КонецЦикла;
3. Обход массива показателей расчета и установки флагов, необходимых данных для каждого способа:
Для Каждого Показатель Из МассивПоказателей Цикл
НеобходимыеДанные.СдельныйЗаработок = НеобходимыеДанные.СдельныйЗаработок Или Показатель = Справочники.ПоказателиСхемМотивации.СдельнаяВыработка;
...
КонецЦикла;
Замечания по доработке:
При добавлении нового способа расчета обязательно приходится добавлять секцию «ИначеЕсли» в текст этой функции. Категорически не рекомендуется для нового способа расчета устанавливать те флаги, которые для его расчета не нужны, так как это понижает производительность! Если новый способ расчета требует новых флагов, то их необходимо добавить в первую часть процедуры.
Функция «ПолучитьДанныеДляРасчета»
Функция «ПолучитьДанныеДляРасчета» является самой важной и самой сложной в общем модуле «ПроведениеРасчетов». Функция анализирует флаги необходимых данных, составляет по ним текст запроса и возвращает выборку из результата запроса, полями которого являются исходные данные для вычисления ВСЕХ видов расчета текущей категории. Текущая категория передается в функцию через один из параметров. Текст функции можно разделить на две части:
1. Определение текстов вложенных запросов с использованием временных таблиц;
2. Анализ флагов необходимых данных, составление по ним текста запроса и установка параметров запроса.
В конце функции выполняется запрос и возвращается результат запроса. Использование итогов в запросе и обход по группировкам необходим для поддержки режима комментирования расчета, а именно для возможности расшифровки базы.
Первая часть находится выше строки:
Запрос = Новый Запрос();
и содержит определение текстов запросов ВСЕХ возможных временных таблиц.
В большинстве вложенных запросов основной таблицей является выборка из рассчитываемого регистра расчета, которая должна соответствовать рассчитываемым записям:
СводныеИндивидуальныеГрафикиТекст =
"ВЫБРАТЬ
| Основной.НомерСтроки КАК НомерСтроки,
|ПОМЕСТИТЬ ВТСводныеИндивидуальныеГрафики
|ИЗ
| РегистрРасчета.ОсновныеНачисленияРаботниковОрганизаций КАК Основной
В подобных запросах используется отбор по регистратору и признаку авторасчета:
|ГДЕ
| Основной.Авторасчет
| И Основной.Регистратор = &ПарамРегистратор
Так как такие вложенные запросы связываются с основным по номеру строки, индексировать их временные таблицы нужно также по номеру строки:
|ИНДЕКСИРОВАТЬ ПО
| НомерСтроки";
Во второй части в зависимости от имени регистра расчета создается текст запроса. Вторую часть функцию можно разделить также на две части:
1. Составление текста секции «ВЫБРАТЬ» (полей выборки) запроса;
2. Добавление в запрос текстов левых соединений.
Первая часть начинается со строки предложения «ВЫБРАТЬ»:
ТекстЗапроса =
"ВЫБРАТЬ
и заканчивается строкой предложения «ИЗ»:
ТекстЗапроса = ТекстЗапроса + "
|ИЗ РегистрРасчета.ОсновныеНачисленияРаботниковОрганизаций Как Основной";
В первой части в зависимости от установленных флагов необходимых данных в секцию «ВЫБРАТЬ» добавляются нужные поля:
Если НеобходимГрафик Тогда
ТекстЗапроса = ТекстЗапроса + ",
| ЕСТЬNULL(СводныеИндивидуальныеГрафики.ОшибкаВводаИндивидуальногоГрафикаРаботы, ЛОЖЬ) КАК ОшибкаВводаИндивидуальногоГрафикаРаботы,
| ЕСТЬNULL(ВремяВведенноеВЦеломЗаПериод.ОшибкаВводаОтработанногоВремени, ЛОЖЬ) КАК ОшибкаВводаОтработанногоВремени,
...
Таблицы «СводныеИндивидуальныеГрафики» и «ВремяВведенноеВЦеломЗаПериод» будут добавлены в текст запроса ниже с помощью левых соединений.
Если установлен флаг «КомментироватьРасчет», то в список полей итогов добавляются дополнительные поля:
Если КомментироватьРасчет Тогда
ПоляЗапросаМаксимум.Добавить("ГрафикРаботыНаименование");
...
КонецЕсли;
Во второй части в запрос добавляются тексты необходимых левых соединений. При этом анализируются флаги необходимых данных. Для каждого флага, если он установлен, заполняются соответствующие временные таблицы:
Если НеобходимГрафик Тогда
Запрос.УстановитьПараметр("ПоДням", Перечисления.ВидыУчетаВремени.ПоДням);
Запрос.УстановитьПараметр("ПоЧасам", Перечисления.ВидыУчетаВремени.ПоЧасам);
...
Если СводныеИндивидуальныеГрафикиТекст <> "ВТСводныеИндивидуальныеГрафики" Тогда
Запрос.Текст = СводныеИндивидуальныеГрафикиТекст;
Запрос.Выполнить();
СводныеИндивидуальныеГрафикиТекст = "ВТСводныеИндивидуальныеГрафики";
КонецЕсли;
...
Запрос.Текст = ВремяВведенноеВЦеломЗаПериодТекст;
Запрос.Выполнить();
ВремяВведенноеВЦеломЗаПериодТекст = "ВТВремяВведенноеВЦеломЗаПериод";
До выполнения вложенного запроса необходимо установить параметры запроса. Если временная таблица могла быть заполнена ранее (при анализе флага выше по тексту функции), то выполнение запроса необходимо помещать в условие (например «СводныеИндивидуальныеГрафикиТекст»), чтобы не выполнить запрос второй раз.
Временные таблицы добавляются к основному запросу с помощью левого соединения:
ТекстЗапроса = ТекстЗапроса + "
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрРасчета.ОсновныеНачисленияРаботниковОрганизаций.ДанныеГрафика(" + Условие + ") Как ДанныеГрафика
|ПО Основной.Регистратор = ДанныеГрафика.Регистратор И Основной.НомерСтроки = ДанныеГрафика.НомерСтроки
|ЛЕВОЕ СОЕДИНЕНИЕ " + СводныеИндивидуальныеГрафикиТекст + " КАК СводныеИндивидуальныеГрафики
|ПО Основной.НомерСтроки = СводныеИндивидуальныеГрафики.НомерСтроки
|ЛЕВОЕ СОЕДИНЕНИЕ " + ВремяВведенноеВЦеломЗаПериодТекст + " КАК ВремяВведенноеВЦеломЗаПериод
|ПО Основной.НомерСтроки = ВремяВведенноеВЦеломЗаПериод.НомерСтроки
В конце составления текста запроса добавляются условия:
ТекстЗапроса = ТекстЗапроса + "
|ГДЕ " + ВнешнееУсловие;
Для начислений внешнее условие определяется так:
ВнешнееУсловие = "Основной.ВидРасчета.КатегорияРасчета = &парамКатегорияНачисления И Основной.Регистратор = &парамРегистратор";
То естьданные для расчета будут получены только для записей текущей категории.
Самыми сложными данными для расчетов, определяемыми в процедуре, являются:
1. Данные о часовой тарифной ставке
2. Данные об отработанном времени из нескольких вложенных запросов с использованием «СпособОпределения» и «ОшибкаОпределения»
3. Данные о норме времени
4. Данные о базе.
Сложность первых трех заключается в том что результаты определяются из нескольких вложенных запросов по определенному приоритету (если результат не получен из одного запроса, то он берется из другого). Для реализации приоритета используются конструкции «ВЫБОР КОГДА» и «ЕСТЬNULL» при составлении текста секции «ВЫБРАТЬ»:
| ВЫБОР
| КОГДА ЕСТЬNULL(ВремяВведенноеПоКаждомуДню.ОшибкаВводаОтработанногоВремени, ЛОЖЬ)
| ТОГДА 0
| ИНАЧЕ ЕСТЬNULL(ВремяВведенноеПоКаждомуДню.ДнейПоТабелю,
| ВЫБОР
| КОГДА ЕСТЬNULL(ВремяВведенноеВЦеломЗаПериод.ОшибкаВводаОтработанногоВремени, ЛОЖЬ)
| ТОГДА 0
| ИНАЧЕ ЕСТЬNULL(ВремяВведенноеВЦеломЗаПериод.ДнейПоТабелю, ВЫБОР
| КОГДА ЕСТЬNULL(СводныеИндивидуальныеГрафики.ОшибкаВводаИндивидуальногоГрафикаРаботы, ЛОЖЬ)
| ТОГДА 0
| ИНАЧЕ ЕСТЬNULL(СводныеИндивидуальныеГрафики.ДнейПоГрафику, ЕСТЬNULL(ВЫБОР
| КОГДА Основной.ВидУчетаВремени = &ПоДням
| ТОГДА ДанныеГрафика.ОсновноеЗначениеФактическийПериодДействия
| ИНАЧЕ ДанныеГрафика.ДополнительноеЗначениеФактическийПериодДействия
| КОНЕЦ, 0))
| КОНЕЦ
| КОНЕЦ
| КОНЕЦ КАК ОтработаноДней,
В данном примере определяется количество отработанных дней по следующему приоритету:
1. Если введен подробный табель, то данные берутся из него
2. Иначе если введен сводный табель, то данные берутся из него
3. Иначе если введен индивидуальный подробный график, то данные берутся из него
4. Иначе если введен сводный индивидуальный график, то данные берутся из него
5. Иначе данные берутся из общего графика
Для того что бы понять из какой временной таблицы определены данные дополнительно применяется поле способа ввода данных «СпособВвода…»:
| ВЫБОР
| КОГДА ЕСТЬNULL(ВремяВведенноеПоКаждомуДню.ОшибкаВводаОтработанногоВремени, ЛОЖЬ)
| ТОГДА NULL
| ИНАЧЕ
| ВЫБОР
| КОГДА НЕ ВремяВведенноеПоКаждомуДню.ОсновноеЗначениеПоТабелю ЕСТЬ NULL
| ТОГДА 4
| ИНАЧЕ
| ВЫБОР
| КОГДА ЕСТЬNULL(ВремяВведенноеВЦеломЗаПериод.ОшибкаВводаОтработанногоВремени, ЛОЖЬ)
| ТОГДА NULL
| ИНАЧЕ
| ВЫБОР
| КОГДА НЕ ВремяВведенноеВЦеломЗаПериод.ОсновноеЗначениеПоТабелю ЕСТЬ NULL
| ТОГДА 1
| ИНАЧЕ
| ВЫБОР
| КОГДА ЕСТЬNULL(СводныеИндивидуальныеГрафики.ОшибкаВводаИндивидуальногоГрафикаРаботы, ЛОЖЬ)
| ТОГДА NULL
| ИНАЧЕ
| ВЫБОР
| КОГДА НЕ СводныеИндивидуальныеГрафики.НормаВремени ЕСТЬ NULL
| ТОГДА 2
| ИНАЧЕ 3
| КОНЕЦ
| КОНЕЦ
| КОНЕЦ
| КОНЕЦ
| КОНЕЦ
| КОНЕЦ КАК СпособВводаВремени
В зависимости от того из какого источника определены данные поле принимает значение 1, 2, 3 или 4. Впоследствии это применяется для комментирования расчета и очень удобно для пользователя. Кроме способа ввода еще определяются поля «ОшибкиВвода» для анализа ошибок данных вложенных запросов. Эти поля тоже используются для комментирования расчета.
При определении данных базы используется самая сложная виртуальная таблица регистра расчета «ДанныеБазы»:
|ИЗ РегистрРасчета.ОсновныеНачисленияРаботниковОрганизаций.БазаОсновныеНачисленияРаботниковОрганизаций(&парамИзмеренияОсновного, &парамИзмеренияБазового, &парамРазрезы, " + Условие + ") Как База1
Для получения базы в нужных разрезах, а также для возможности расшифровки базы по базовым видам расчета при комментировании используются разрезы («&парамРазрезы»).
Замечания по доработке:
Если в структуру необходимых данных был добавлен новый флаг, то его обработку необходимо добавить в эту функцию. Надо помнить, что добавлять надо строки как в секцию «ВЫБРАТЬ» так и в секцию добавления левых соединений. Если флагу необходимы временные таблицы, то тексты их определения необходимо поместить в первую часть функции по аналогии с существующими. Временные таблицы вложенных запросов обязательно индексировать по тем полям, по которым они будут связанны в левом соединении с основным запросом (в основном, это номер строки).
В запросах к регистру расчета нужно применять условия на регистратор и признак авторасчета:
|ГДЕ
| Основной.Авторасчет
| И Основной.Регистратор = &ПарамРегистратор
Не помешает условие и на категорию расчета, но в типовой конфигурации почему-то не применяется.
Для повышения производительности во внешнее условие, которое ограничивает состав записей, для которых определяются необходимые данные, можно добавить условие на признак авторасчета.
Для определения данных базы используется виртуальная таблица «ДанныеБазы», у которой имеются определенные проблемы с производительностью (вероятно из-за расчета с использованием пропорции по норме времени из графика). Для повышения производительности можно переписать получение данных базы без использования виртуальной таблицы:
|ЛЕВОЕ СОЕДИНЕНИЕ (
| ВЫБРАТЬ
| Основной.НомерСтроки КАК НомерСтроки,
| Основной.Сотрудник КАК Сотрудник,
| НАЧАЛОПЕРИОДА(Основной.БазовыйПериодНачало, МЕСЯЦ) КАК ДатаНачала,
| Основной.ВидРасчета КАК ВидРасчета,
| СУММА(БазаИзНачислений.Результат) КАК РезультатБаза
| ИЗ РегистрРасчета.ОсновныеНачисленияРаботниковОрганизаций КАК Основной
|
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрРасчета.ОсновныеНачисленияРаботниковОрганизаций КАК БазаИзНачислений
| ПО Основной.Сотрудник = БазаИзНачислений.Сотрудник
| И НАЧАЛОПЕРИОДА(БазаИзНачислений.ПериодДействия, МЕСЯЦ) = НАЧАЛОПЕРИОДА(Основной.БазовыйПериодНачало, МЕСЯЦ)
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПланВидовРасчета.ОсновныеНачисленияОрганизаций.БазовыеВидыРасчета КАК БазовыеВидыРасчета
| ПО Основной.ВидРасчета = БазовыеВидыРасчета.Ссылка
| И БазаИзНачислений.ВидРасчета = БазовыеВидыРасчета.ВидРасчета
|
| ГДЕ Основной.ВидРасчета.КатегорияРасчета = &парамКатегорияНачисления И Основной.Регистратор = &парамРегистратор И Основной.Авторасчет
| СГРУППИРОВАТЬ ПО
| Основной.Сотрудник,
| Основной.НомерСтроки,
| Основной.БазовыйПериодНачало,
| Основной.ВидРасчета) Как База
|ПО База.НомерСтроки = Основной.НомерСтроки
Еще лучше оформить это с применением временной таблицы. Если необходима расшифровка по базовым видам расчета, то в группировку необходимо добавить «БазаИзНачислений.ВидРасчета».
Процедура «РассчитатьЗаписиНабора»
Процедура «РассчитатьЗаписиНабора» рассчитывает записи набора регистра расчета.
Запись набора рассчитывается только если для нее найдены необходимые данные для расчета:
СтруктураПоиска.НомерСтроки = СтрокаДвижений.НомерСтроки;
Если ИсходныеДанные.НайтиСледующий(СтруктураПоиска) Тогда
Если в процедуру через параметр передана табличная часть документа, то выполняется поиск нужных строк для заполнения в них результатов расчета из рассчитанной строки набора:
СтрокаТабличнойЧасти = Неопределено;
Если ТабличнаяЧастьДокумента <> Неопределено Тогда
…
СтрокаТабличнойЧасти = ТабличнаяЧастьДокумента.Получить(НаборЗаписейРегистра.Индекс(СтрокаДвижений));
Если передан массив конкретных строк документа, то строка табличной части определяется:
СтрокаТабличнойЧасти = ТабличнаяЧастьДокумента.Получить(МассивИндексыСтрокТабличнойЧасти.Получить(НаборЗаписейРегистра.Индекс(СтрокаДвижений)));
Далее вызывается процедура «РассчитатьЗаписьРегистраРасчета», в которой происходит непосредственный расчет записи:
РассчитатьЗаписьРегистраРасчета(СтрокаДвижений, ИсходныеДанные, НеобходимыеДанные, УправленческиеРасчеты, РегламентированныеНачисления, РегламентированныеУдержания, ОсновныеНачисленияОрганизаций, ВзносыВФонды, КомментироватьРасчет, ФормаКомментариев, ГрафикиБезРабочегоВремени);
Результаты расчета при этомсохраняются в рассчитываемой записи. Стоит отметить, что результатом расчета является не только рассчитанная денежная сумма, но и оплачиваемое время (в днях или часах).
Если определена строка табличной части документа (см. выше), то данные расчета (сумма и оплачиваемой время) записываются в строку табличной части.
Если СтрокаТабличнойЧасти <> Неопределено Тогда
СтрокаТабличнойЧасти.Результат = СтрокаДвижений.Результат;
Если ОсновныеНачисленияОрганизаций И СтрокаДвижений.Авторасчет Тогда
СтрокаТабличнойЧасти.ОплаченоДнейЧасов = СтрокаДвижений.ОплаченоДнейЧасов;
...
КонецЕсли;
КонецЕсли;
В зависимости от установленных в виде расчета признаков (ЗачетОтработанногоВремени, ЗачетНормыВремени и ЗачетКалендарных) в рассчитанной записи и, если надо, в строке табличной части заполняются реквизиты об отработанном времени, норме времени и календарных днях.
Замечания по доработке:
Данная процедура почти никогда не требует доработок.
Процедура «РассчитатьЗаписьРегистраРасчета»
Процедура «РассчитатьЗаписьРегистраРасчета» производит непосредственный расчет записи. В этой процедуре выполняется связанный с указанным в записи способом расчета алгоритм. Данные для расчета содержатся в записи набора («СтрокаДвижений») и структуре «ИсходныеДанные», которые передаются в процедуру как параметры. Процедура организована в виде набора блоков «ИначеЕсли» с проверкой на способ расчет:
Если СпособРасчета = Перечисления.СпособыРасчетаОплатыТруда.Процентом Тогда
...
ИначеЕсли СпособРасчета = Перечисления.СпособыРасчетаОплатыТруда.ГосударственноеПособие Тогда
...
ИначеЕсли СпособРасчета = Перечисления.СпособыРасчетаОплатыТруда.ФиксированнойСуммой Тогда
...
Иначе // Не найден способ расчета
ОшибкаРасчета(ИсходныеДанные.СотрудникНаименование + "; " + ИсходныеДанные.ВидРасчетаНаименование + ": вид расчета не предусмотрен");
КонецЕсли;
В каждом блоке реализован алгоритм расчета для одного способа расчета. Блоков «ИначеЕсли» столько, сколько способов расчета поддерживает конфигурация.
Результат расчета сохраняется в локальной переменной модуля «Результат»:
Результат = ИсходныеДанные.РезультатБаза * СтрокаДвижений[ПоказательРазмер]/100;
В конце процедуры результат сохраняется в строке движения в зависимости от признака «Сторно» с положительным или отрицательным знаком:
Если СтрокаДвижений.Сторно Тогда
СтрокаДвижений.Результат = Результат * -1;
...
Иначе
СтрокаДвижений.Результат = Результат;
КонецЕсли;
Большинство реальных начислений оплачивают определенное время (отработанное или не отработанное). И выше было указано, что результатом расчета является не только сумма начисления, но и оплачиваемое время в днях или часах (реквизит «ОплаченоДнейЧасов» строки движений). Когда оплачивается отработанное время, то оно определяется с помощью функции «ОплачиваемоеВремя»:
ОтработаноВремениОсновное = ОплачиваемоеВремя(ИсходныеДанные, СтрокаДвижений, КомментироватьРасчет, КомментарийВидаРасчета, , ГрафикиБезРабочегоВремени);
...
СтрокаДвижений.ОплаченоДнейЧасов = ОтработаноВремениОсновное * ?(СтрокаДвижений.Сторно,-1,1);
Функция «ОплачиваемоеВремя» будет рассмотрена ниже.
В конфигурации предусмотрен режим проведения расчетов с комментированием (в процессе расчета выдаются сообщения со значениями параметров и промежуточных результатов). Для способов расчетов, использующих базу (сумму результатов других начислений) при комментировании расшифровывается база до базовых видов расчета. Для этого используются детальные записи из результата запроса «ИсходныеДанные»:
РасшифровкаБазы = ИсходныеДанные.Выбрать();
Пока РасшифровкаБазы.Следующий() Цикл
Если РасшифровкаБазы.РезультатБаза <> 0 Тогда
КомментарийРасчета(РасшифровкаБазы.ВидРасчетаРазрезНаименование + ": " + РасшифровкаБазы.РезультатБаза, КомментарийБазы);
КонецЕсли;
КонецЦикла;
Замечания по доработке:
При добавлении нового способа расчета в эту процедуру необходимо добавить блок «ИначеЕсли» с реализацией алгоритма расчета для нового способа.
В этой процедуре допускается применение только простейших действий (арифметические, максимум, минимум) с исходными данными и рассчитываемой строкой движений набора,не должно быть никаких запросов. Все, что необходимо для расчета УЖЕ должно быть в структуре «ИсходныеДанные».
Функция «ОплачиваемоеВремя»
Функция «ОплачиваемоеВремя» вызывается при расчете определенного вида начисления: при обработке конкретного способа расчета в процедуре «РассчитатьЗаписьРегистраРасчета» и необходима для:
1. Определения оплачиваемого времени;
2. Анализа ошибок определения отработанного времени;
3. Формирования текстов комментариев (если включен режим комментирования)
В конфигурациях эта функция применяется только для начислений за отработанное время. Оплачиваемое время определяется исходя из вида времени (Отработанное/Неотработанное) и вида учета времени (ПоДням/ПоЧасам).
Анализ ошибок определения отработанного времени происходит с помощью проверки значения поля «ОшибкиВвода» исходных данных. При этом формируются соответствующие сообщения об ошибках.
Если включен режим комментирования, то функция формирует интерактивные тексты комментариев определения оплачиваемого времени в зависимости от значения поля «СпособВвода» исходных данных. Интерактивность заключается в возможности открытия объектов (справочников, документов) по клику мышки на тексте описания этого объекта в комментарии.
Замечания по доработке:
Если изменялись алгоритмы формирования полей «СпособВвода» или «ОшибкиВвода» при определении отработанного времени в функции «ПолучитьДанныеДляРасчета», то необходимо изменить обработку этих полей в этой функции.
Почему все так сложно…
В начале статьи были описаны две важные цели разработки конфигурации ЗУП. Работа общего модуля «Проведения расчетов» организована с учетом первой цели: «Скорость (производительность) выполнения расчетов». Самая трудоемкая операция – это получение исходных данных для расчетов организована таким образом, что данные для расчета всех записей набора определенной категории выбираются за один раз. При этом вся нагрузка ложится на SQL сервер.
Добавление нового способа расчета
Для добавления нового вида расчета необходимо добавить блоки «ИначеЕсли» в следующие процедуры и функции:
1. ПроведениеРасчетов.ВизуализироватьФормулуРасчета
2. ПроведениеРасчетов.ПолучитьСтруктуруНеобходимыхДанных
3. ПроведениеРасчетов.РасчитатьЗаписьРегистраРасчета
4. РаботаСДиалогами.ПолучитьСведенияОВидеРасчетаСхемыМотивации
В следующие процедуры надо просто добавить (если необходимо) способ расчета в список
1. ПроведениеРасчетов.РассчитатьЗаписиНабора
2. ПроведениеРасчетов.ПолучитьСписокОсновныхВариантовНачисленийОрганизации
3. ПроведениеРасчетов.ПолучитьСписокСпособовРасчетаНеТребующихУказанияВалюты
4. ПроведениеРасчетов.СпособРасчетаНеТребуетВалюты
Если в структуру необходимых данных были добавлены новые флаги, то их обработка должна производиться в функции "ПолучитьДанныеДляРасчета".
Сравнение с конфигурацией ЗиК платформы версии 7.7
Данный материал полезен программистам, имеющим опыт работы с конфигурацией ЗиК 7.7.
Сравнение компоненты «Расчет»7.7 и платформы 8 в части расчетов:
Понятие платформы 7.7 |
Аналог в платформе 8.х |
Примечание к 8.х |
Журнал расчетов |
Регистр расчета |
Автоматически вытеснение не срабатывает и перерасчеты тоже. Записи можно вносить только документами. |
Запись журнала расчетов |
Запись регистра расчетов |
|
Признак «Сторно» |
Встроенный реквизит регистра расчетов |
|
Признак «Перерасчет» |
Встроенного реквизита регистра нет |
|
Признак «Исправлена» |
Встроенного реквизита регистра нет |
|
Текущий период регистра расчетов |
Нет такого понятия |
Записи могут вносится в регистр расчета с любым периодом регистрации |
Виды расчетов |
Планы видов расчета |
Виды расчетов можно добавлять в режиме предприятия |
Приоритет вида расчет |
Нет такого понятия |
Прикладная конфигурация должна самостоятельно следить за последовательностью расчета. |
Группы расчетов |
Нет такого понятия |
В плане видов расчета есть предопределенная табличная часть «Базовые», которая используется для автоматического получения базы |
Вытеснение |
В плане видов расчета есть предопределенная табличная часть «Вытесняющие» |
Список вытесняющих видов расчета можно изменять в режиме пользователя. |
Перерасчеты |
В плане видов расчета есть предопределенная табличная часть «Ведущие» |
Автоматически перерасчеты не производятся, а только регистрируется их необходимость в отдельных таблицах |
Календари |
Нет такого понятия |
В прикладной конфигурации графики работы и календари реализованы с применением регистров сведений |
По сравнению с 8 в 7.7 программист делал меньше, а платформа больше. Из-за этого иногда не хватало универсальности. В 8 универсальности больше, но и программисту работы добавилось.
Сравнение конфигураций ЗиК7.7 и ЗУП8:
Понятие в ЗиК7.7 |
Аналог в ЗУП8 |
Период регистрации определяется по текущему периоду регистра расчета |
Период регистрации указывается в отдельном реквизите шапки каждого документа |
Справочник «Шкала ставок» |
Больше десятка регистров сведений для хранения всех законодательно установленных значений |
Справочник «Подразделения» |
Справочник «ПодразделенияОрганизации»
|
Справочник «Должности» |
Справочник «ДолжностиОрганизации» |
Справочник «Штатное расписание». Место работы (ячейка штатного расписания) это справочник штатное расписание с реквизитом «Должность», подчиненный справочнику «Подразделения», то есть Подразделение+Должность. |
Регистр сведений «Штатное расписание». Измерениями регистра являются «ПодразделенияОрганизации» и «ДолжностиОрганизации», то есть тоже Подразделение+Должность |
Справочник «ФизическиеЛица» |
Справочник «ФизическиеЛица» |
Справочники «Сотрудники» |
Справочники «Сотрудники» и |
Справочник «Назначения» |
Регистр сведений «РаботникиОрганизаций» |
Справочник «ДопРасчеты» |
Регистры сведений «ПлановыеНачисления/Удержания» |
Взносы в фонды на ФОТ тоже учитываются в регистре расчета (в том же где и взносы с работника «Взносы в фонды»)
НДФЛ учитывается только в регистре накопления остатков «Взаиморасчеты по НДФЛ»
Используются регистры накопления остатков (переходящие остатки считаются автоматически):
- Взаиморасчеты с сотрудниками
- Взаиморасчеты по НДФЛ
- Взаиморасчеты по взносам в фонды
- Ссуды
В ЗУП в отличие от ЗиК есть удобная возможность ручного исправления результатов всех автоматических расчетов.
В ЗиК расчет каждой записи журнала расчетов происходит независимо от остальных. При этом все данные для расчета надо выбирать из базы заново (даже если мы их уже выбирали для расчета предыдущей записи). То есть при расчете каждой записи данные выбираются из базы, рассчитываются и в базу записывается результат.
В ЗУП сначала выбираются данные из базы для расчета всех (почти всех с учетом зависимости) записей регистра расчета, выполняется расчет и результаты расчета записываются в базу опять для всех записей.