Как правильно настроить автоматический расчет расценок при изменении тарифных ставок в 1С

Программист 1С v8.3 (Обычные формы) Управленческий учет IT и автоматизация бизнеса
← На главную

В практической работе разработчика 1С часто возникает задача синхронизации и автоматического пересчета связанных данных. Типичный пример: в системе ведется справочник технологических операций или работ, стоимость которых напрямую зависит от базовых тарифных ставок. При изменении тарифа система должна автоматически обновить расценки для всех связанных работ.

В этой статье мы подробно разберем, как решить эту задачу на примере конфигурации «КАМИН:Расчет заработной платы 3.0» (или любой другой аналогичной конфигурации на платформе «1С:Предприятие 8»). Мы проанализируем архитектурные ошибки, которые часто допускают начинающие специалисты, и выработаем надежное, быстродействующее и отказоустойчивое решение с использованием подписок на события.

---

Анализ архитектурных ошибок: почему простые решения не работают

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

Ошибка №1: Подписка на событие для справочника вместо регистра

В ходе обсуждения автору посоветовали создать подписку на событие ПриЗаписи для справочника ТарифныеСетки. Это не сработает. В конфигурациях расчета зарплаты сам справочник «Тарифные сетки» содержит только общую информацию (например, наименование). Конкретные значения ставок (тарифы по разрядам) хранятся в подчиненном периодическом регистре сведений (в нашем случае это РазрядыТарифныхСеток). Когда расчетчик меняет ставку, он вносит изменения в регистр сведений, а сам элемент справочника тарифных сеток не перезаписывается. Следовательно, подписка на справочник просто не запустится.

Правильное решение: Нам необходимо создавать подписку на событие для источника РегистрСведенийНаборЗаписей.РазрядыТарифныхСеток.

Ошибка №2: Игнорирование периодичности и истории изменений

Если мы будем записывать вычисленную «Расценку» напрямую в реквизит справочника Работы, мы потеряем историю изменений. Тарифные ставки периодически индексируются. Например, до 1 июня ставка составляла 200 рублей, а с 1 июня — 250 рублей. Если мы перезапишем расценку прямо в карточке работы, то при попытке пересчитать или проанализировать документы за прошлые периоды программа возьмет новую (неактуальную для того периода) расценку, что приведет к искажению учета.

Правильное решение: Мы рассмотрим два варианта реализации — динамический расчет «на лету» без хранения расценки (Вариант А) и использование нового периодического регистра сведений для хранения истории расценок (Вариант Б).

---

Пошаговая реализация задачи

Выясним причину и разберем по шагам правильный алгоритм реализации задачи.

Шаг 1. Связывание справочников

Для того чтобы система знала, какую ставку применять к конкретной работе, нам нужно связать справочник Работы со справочником ТарифныеСетки. Добавим в справочник Работы новый реквизит:

Если в вашей тарифной сетке используется разрядность, также потребуется добавить реквизит Разряд (тип данных зависит от структуры регистра ставок в вашей конфигурации, обычно это справочник или число).

Шаг 2. Выбор стратегии хранения расценок

Вариант А (Рекомендуемый): Динамический расчет без хранения в справочнике

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

Формула расчета в запросе будет выглядеть следующим образом:

Расценка = РазрядыТарифныхСетокСрезПоследних.Ставка * Работы.НормаЧасов

Этот способ исключает дублирование данных в базе и гарантирует 100% актуальность расценок на любую дату в прошлом или будущем.

Вариант Б: Хранение истории расценок в периодическом регистре сведений

Если бизнес-логика требует физического хранения уже рассчитанных расценок в базе данных, создадим новый периодический регистр сведений РасценкиРабот со следующей структурой:

---

Шаг 3. Создание подписки на событие

Разберем процесс создания подписки в дереве метаданных конфигуратора:

  1. Перейдем в ветку Общие -> Подписки на события и добавим новую подписку.
  2. Зададим имя подписки: ПриЗаписиСтавокТарифныхСеток.
  3. В поле Источник выберем объект: РегистрСведенийНаборЗаписей.РазрядыТарифныхСеток.
  4. В поле Событие выберем значение: ПриЗаписи.
  5. В поле Обработчик нажмите на лупу. Нам предложат выбрать общий модуль. Создадим новый общий модуль для нашей логики.

Шаг 4. Настройка свойств Общего модуля

При создании общего модуля (назовем его ОбработчикиСобытийКастомный) критически важно правильно выставить свойства в палитре свойств:

---

Шаг 5. Написание оптимального программного кода

Посмотрим на пример реализации обработчика подписки в общем модуле. Код должен быть максимально оптимизирован, не вызывать лишних блокировок и корректно обрабатывать пакетную загрузку данных.

Проанализируем структуру кода:


Процедура ПриЗаписиСтавокТарифныхСетокПриЗаписи(Источник, Отказ) Экспорт
	
	// Критически важное правило: если идет загрузка данных (обмен, перенос), 
	// отключаем выполнение бизнес-логики во избежание падения производительности.
	Если Источник.ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	// Получаем измененные записи регистра ставок.
	// Так как Источник — это НаборЗаписей, мы можем проанализировать, для каких именно
	// тарифных сеток и на какие даты поменялись ставки.
	ТаблицаИзменений = Источник.Выгрузить();
	
	 Если ТаблицаИзменений.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;
	
	// Чтобы избежать дублирования запросов в цикле, соберем все уникальные тарифные сетки 
	// и даты (периоды), для которых изменились ставки.
	ТарифныеСеткиМассив = Новый Массив;
	Для Каждого СтрокаИзменений Из ТаблицаИзменений Цикл
		Если ТарифныеСеткиМассив.Найти(СтрокаИзменений.ТарифнаяСетка) = Неопределено Тогда
			ТарифныеСеткиМассив.Добавить(СтрокаИзменений.ТарифнаяСетка);
		КонецЕсли;
	КонецЦикла;
	
	// Запросом выбираем все работы, привязанные к измененным тарифным сеткам,
	// и получаем актуальные ставки из регистра на даты изменения.
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	Работы.Ссылка КАК Работа,
		|	Работы.НормаЧасов КАК НормаЧасов,
		|	РазрядыТарифныхСеток.Период КАК Период,
		|	РазрядыТарифныхСеток.Ставка КАК Ставка
		|ИЗ
		|	Справочник.Работы КАК Работы
		|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.РазрядыТарифныхСеток КАК РазрядыТарифныхСеток
		|		ПО Работы.ТарифнаяСетка = РазрядыТарифныхСеток.ТарифнаяСетка
		|ГДЕ
		|	Работы.ТарифнаяСетка В(&ТарифныеСеткиМассив)";
		
	Запрос.УстановитьПараметр("ТарифныеСеткиМассив", ТарифныеСеткиМассив);
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	// Записываем новые рассчитанные значения расценок.
	// В данном примере реализуем запись в созданный нами периодический регистр "РасценкиРабот".
	Пока Выборка.Следующий() Цикл
		
		Если Выборка.НормаЧасов = 0 Или Выборка.Ставка = 0 Тогда
			Продолжить; // Пропускаем некорректные данные
		КонецЕсли;
		
		НоваяРасценка = Выборка.Ставка * Выборка.НормаЧасов;
		
		// Используем менеджер записи для точечного обновления/создания записи в регистре расценок
		МенеджерЗаписи = РегистрыСведений.РасценкиРабот.СоздатьМенеджерЗаписи();
		МенеджерЗаписи.Период    = Выборка.Period;
		МенеджерЗаписи.Работа    = Выборка.Работа;
		
		// Попытаемся прочитать существующую запись, чтобы избежать лишней перезаписи
		МенеджерЗаписи.Прочитать();
		Если МенеджерЗаписи.Выбран() Тогда
			Если МенеджерЗаписи.Расценка = НоваяРасценка Тогда
				Продолжить; // Если расценка не изменилась, ничего не делаем
			КонецЕсли;
		КонецЕсли;
		
		// Заполняем новые значения и записываем
		МенеджерЗаписи.Период    = Выборка.Период;
		МенеджерЗаписи.Работа    = Выборка.Работа;
		МенеджерЗаписи.Расценка  = НоваяРасценка;
		
		Попытка
			МенеджерЗаписи.Записать(Истина);
		Исключение
			ЗаписьЖурналаРегистрации("Ошибка автоматического пересчета расценок",
				УровеньЖурналаРегистрации.Ошибка,
				Выборка.Работа.Метаданные(),
				Выборка.Работа,
				ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		КонецПопытки;
		
	КонецЦикла;
	
КонецПроцедуры

---

Важные правила разработки подписок в 1С

Рассмотрим подробнее ключевые правила, которые гарантируют стабильность системы при работе с подписками:

  1. Проверка на загрузку данных: Строка Если Источник.ОбменДанными.Загрузка Тогда Возврат; КонецЕсли; должна быть первой в любом обработчике. При обмене данными (РИБ, интеграции) система переносит готовые согласованные данные. Повторный запуск расчетов в этот момент может вызвать рассинхронизацию баз, ошибки блокировок и замедление работы в сотни раз.
  2. Минимизация кода в транзакции: Событие ПриЗаписи выполняется внутри транзакции записи основного объекта. Старайтесь писать максимально быстрые и легкие запросы. Никогда не делайте длительных вычислений, обращений к веб-сервисам или ресурсоемких операций внутри обработчика подписки.
  3. Логирование ошибок: Обязательно оборачивайте запись связанных объектов в блоки Попытка...Исключение с выводом информации в Журнал регистрации. Ошибка в коде подписки не должна блокировать пользователю возможность изменить ставку тарифа, если по какой-то причине одна из сотен связанных работ оказалась повреждена или заблокирована.

Мы успешно проанализировали ситуацию, исправили концептуальные ошибки форума и создали надежное расширяемое решение для автоматического расчета расценок в системе 1С.

← На главную