Вытеснение по периоду действия в 1С — это механизм регистра расчёта, который исключает из периода действия одной записи те дни, где действует запись вытесняющего вида расчёта. Классический пример: оклад начисляется за месяц, но командировка с 3 по 5 число его вытесняет, и оклад должен считаться только за оставшиеся рабочие дни. Платформа сама строит фактический период действия — набор интервалов, где запись реально действует, — и отдаёт его через виртуальные таблицы регистра.

Ниже — пример на учебной модели: документ «Начисление зарплаты», регистр расчёта ОсновныеНачисления с поддержкой периода действия и график работы. Покажу, что настроить, как это выглядит в запросах и в каком порядке писать код.

Исходная модель

Для примера используем:

  • план видов расчёта ОсновныеНачисления с видами Оклад и Командировка, у плана включено использование периода действия;
  • регистр расчёта ОсновныеНачисления с измерением Сотрудник, ресурсом Сумма, реквизитами График и Параметр (размер оклада); у регистра указан регистр сведений с графиком работы и связь по реквизиту График;
  • документ НачислениеЗарплаты с табличной частью: сотрудник, вид расчёта, даты начала и окончания, размер, график.

Для наглядности добавим в регистр два поля:

ПолеВидЗачем
ОтработаноЧасовРесурсФактически отработанные часы; по ресурсам можно получать базу для других начислений, например премии
ЧасовПланРеквизитНорма часов месяца — справочная информация

Реквизиты регистра расчёта не участвуют ни в вытеснении, ни в получении базы. Если значение понадобится как база для другого расчёта, делайте его ресурсом.

Настройка вытеснения в плане видов расчёта

  1. Откройте план видов расчёта и убедитесь, что включено использование периода действия.
  2. Добавьте вид расчёта Командировка.
  3. У вида расчёта Оклад на закладке вытесняющих видов добавьте Командировка.

Ограничения, о которых стоит помнить:

  • вытесняющий и вытесняемый виды должны принадлежать одному плану видов расчёта и иметь протяжённость во времени — для разовых начислений вытеснение не имеет смысла;
  • список вытесняющих видов должен быть полным: если Командировка вытесняет Оклад, а Больничный вытесняет Командировку, то Больничный должен быть и в списке оклада;
  • в режиме предприятия список можно только дополнить, но не сократить.

Как платформа строит фактический период действия

Вытеснение по периоду действия 1С: интервал оклада разделён командировкой на две части
Вытеснение по периоду действия 1С: интервал оклада разделён командировкой на две части

Фактический период пересчитывается при каждой записи набора записей регистра. Платформа учитывает:

  • пересечение периодов действия записей (в примере — записей одного сотрудника);
  • списки вытесняющих видов расчёта;
  • период регистрации: записи с более ранним периодом регистрации безусловно приоритетны, поздние их не вытесняют независимо от списков;
  • сторно: сторнирующая запись действует, только если в её интервале есть фактически действующая более ранняя запись того же вида.

Полностью вытесненная запись получает пустой фактический период действия — она остаётся в регистре, но в расчётах «по факту» не участвует.

Запросы: ДанныеГрафика и ФактическийПериодДействия

Виртуальная таблица ДанныеГрафика возвращает по каждой записи значения ресурса графика за разные периоды. Для ресурса графика КоличествоЧасов платформа формирует поля КоличествоЧасовПериодДействия (норма за весь период действия) и КоличествоЧасовФактическийПериодДействия (за фактический период после вытеснения).

Запрос 1С
ВЫБРАТЬ
	ДанныеГрафика.НомерСтроки КАК НомерСтроки,
	ДанныеГрафика.Параметр КАК Оклад,
	ЕСТЬNULL(ДанныеГрафика.КоличествоЧасовПериодДействия, 0) КАК ЧасовПлан,
	ЕСТЬNULL(ДанныеГрафика.КоличествоЧасовФактическийПериодДействия, 0) КАК ЧасовФакт
ИЗ
	РегистрРасчета.ОсновныеНачисления.ДанныеГрафика(
			Регистратор = &Регистратор
				И ВидРасчета = &ВидРасчетаОклад) КАК ДанныеГрафика

Виртуальная таблица ФактическийПериодДействия показывает сами интервалы. Удобно смотреть её в консоли запросов при отладке:

Запрос 1С
ВЫБРАТЬ
	ФактическийПериод.Сотрудник КАК Сотрудник,
	ФактическийПериод.ВидРасчета КАК ВидРасчета,
	ФактическийПериод.ПериодДействияНачало КАК Начало,
	ФактическийПериод.ПериодДействияКонец КАК Окончание,
	ФактическийПериод.ОтработаноЧасов КАК ОтработаноЧасов
ИЗ
	РегистрРасчета.ОсновныеНачисления.ФактическийПериодДействия(
			Регистратор = &Регистратор
				И ВидРасчета = &ВидРасчетаОклад) КАК ФактическийПериод

До ввода командировки запрос вернёт одну строку по окладу — весь период. После вытеснения строк станет две: до командировки и после неё. Остальные поля таблицы повторяют поля самой записи регистра.

Код: проведение документа и расчёт оклада

Порядок важен: сначала записываем движения, чтобы платформа вычислила фактический период, потом читаем данные графика и считаем суммы, потом записываем ещё раз.

1С
Процедура ОбработкаПроведения(Отказ, РежимПроведения)

	Движения.ОсновныеНачисления.Записывать = Истина;
	Движения.ОсновныеНачисления.Очистить();

	ПериодРегистрации = НачалоМесяца(Дата);

	Для Каждого СтрокаНачисления Из Начисления Цикл
		Движение = Движения.ОсновныеНачисления.Добавить();
		Движение.ПериодРегистрации = ПериодРегистрации;
		Движение.ВидРасчета = СтрокаНачисления.ВидРасчета;
		Движение.Сотрудник = СтрокаНачисления.Сотрудник;
		Движение.ПериодДействияНачало = СтрокаНачисления.ДатаНачала;
		Движение.ПериодДействияКонец = КонецДня(СтрокаНачисления.ДатаОкончания);
		Движение.График = СтрокаНачисления.График;
		Движение.Параметр = СтрокаНачисления.Размер;

		// Командировка в учебном примере начисляется фиксированной суммой.
		Если СтрокаНачисления.ВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Командировка Тогда
			Движение.Сумма = СтрокаНачисления.Размер;
		КонецЕсли;
	КонецЦикла;

	// Первая запись: платформа вычисляет фактический период действия.
	Движения.ОсновныеНачисления.Записать();

	РасчетЗарплаты.РассчитатьОклад(Ссылка, Движения.ОсновныеНачисления);

КонецПроцедуры

Расчёт оклада вынесен в общий модуль РасчетЗарплаты. Вместо поиска по выборке с Сбросить() на каждой итерации я выгружаю результат в таблицу значений и индексирую её по номеру строки — так код проще и не деградирует на больших наборах.

1С
// Рассчитывает оклад пропорционально отработанным часам с учетом вытеснения.
//
// Параметры:
//   Регистратор - ДокументСсылка.НачислениеЗарплаты - документ-регистратор.
//   НаборЗаписей - РегистрРасчетаНаборЗаписей.ОсновныеНачисления - записанные движения.
//
Процедура РассчитатьОклад(Регистратор, НаборЗаписей) Экспорт

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ДанныеГрафика.НомерСтроки КАК НомерСтроки,
	|	ДанныеГрафика.Параметр КАК Оклад,
	|	ЕСТЬNULL(ДанныеГрафика.КоличествоЧасовПериодДействия, 0) КАК ЧасовПлан,
	|	ЕСТЬNULL(ДанныеГрафика.КоличествоЧасовФактическийПериодДействия, 0) КАК ЧасовФакт
	|ИЗ
	|	РегистрРасчета.ОсновныеНачисления.ДанныеГрафика(
	|			Регистратор = &Регистратор
	|				И ВидРасчета = &ВидРасчетаОклад) КАК ДанныеГрафика";
	Запрос.УстановитьПараметр("Регистратор", Регистратор);
	Запрос.УстановитьПараметр("ВидРасчетаОклад", ПланыВидовРасчета.ОсновныеНачисления.Оклад);

	ДанныеРасчета = Запрос.Выполнить().Выгрузить();
	ДанныеРасчета.Индексы.Добавить("НомерСтроки");

	Для Каждого Запись Из НаборЗаписей Цикл
		СтрокаДанных = ДанныеРасчета.Найти(Запись.НомерСтроки, "НомерСтроки");
		Если СтрокаДанных = Неопределено Тогда
			Продолжить; // Запись другого вида расчета.
		КонецЕсли;

		Если СтрокаДанных.ЧасовПлан = 0 Тогда
			Запись.Сумма = 0;
		Иначе
			Запись.Сумма = Окр(СтрокаДанных.Оклад * СтрокаДанных.ЧасовФакт / СтрокаДанных.ЧасовПлан, 2);
		КонецЕсли;
		Запись.ЧасовПлан = СтрокаДанных.ЧасовПлан;
		Запись.ОтработаноЧасов = СтрокаДанных.ЧасовФакт;
	КонецЦикла;

	// Повторная запись с замещением сохраняет рассчитанные суммы.
	НаборЗаписей.Записать(Истина);

КонецПроцедуры

Что получается в результате

Проверка формулы на цифрах: норма месяца — 184 часа, оклад — 60 000, в документе оклад действует с 1 по 10 число, это 64 рабочих часа по графику. Сумма: 60 000 × 64 / 184 = 20 869,57.

Добавляем командировку с 3 по 5 число и перепроводим документ:

  • по командировке начислена фиксированная сумма;
  • ОтработаноЧасов по окладу уменьшился с 64 до 56 — вытеснен один рабочий день;
  • 4 и 5 число по графику выходные, поэтому три календарных дня командировки «съели» только 8 рабочих часов;
  • ФактическийПериодДействия по окладу вернёт два интервала: 1–2 и 6–10 число.

Это и есть главный смысл механизма: вы не вычитаете дни вручную, а описываете правила, и платформа сама считает пересечения с учётом графика.

Типичные ошибки

  • Расчёт до записи движений. Если читать ДанныеГрафика до первой записи набора, фактический период ещё не вычислен, и суммы будут по полному периоду.
  • Неполный список вытесняющих видов. Цепочки вытеснения должны быть замкнуты, иначе результат будет неожиданным.
  • Разные периоды регистрации. Командировка, введённая в следующем месяце, не вытеснит уже начисленный оклад прошлого месяца — нужен сторно и перерасчёт.
  • Попытка вытеснять разовые начисления. Премия без протяжённости во времени в механизме не участвует.
  • Ожидание вытеснения по реквизитам. Реквизиты регистра — только справочные данные.

В типовой «1С:ЗУП» механизмы расчёта устроены сложнее, но на тех же принципах. Если нужно добавить свой вид начисления или поправить расчёт без потери обновляемости, это задача доработки 1С:ЗУП; собственные расчётные подсистемы проектирую в рамках разработки конфигураций 1С.

Итог

  • Вытеснение по периоду действия исключает из периода записи дни, где действует вытесняющий вид расчёта.
  • Настраивается списком вытесняющих видов в плане видов расчёта; список должен быть полным.
  • Фактический период пересчитывается при каждой записи набора, поэтому сначала пишем движения, потом считаем.
  • ДанныеГрафика даёт норму и факт по графику, ФактическийПериодДействия — сами интервалы.
  • Более ранний период регистрации всегда приоритетен: прошлые периоды исправляют сторнированием.