Как реализовать автоматическую подстановку премии сотрудника на основании данных о продажах в 1С 8.3?

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

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

Анализ исходных данных и постановка задачи

Для начала проанализируем структуру метаданных, с которыми нам предстоит работать. У нас есть документ НачисленияСотрудникам с табличной частью Начисления. В этой табличной части присутствуют реквизиты: Сотрудник, Должность, Оклад и Премия. Информацию о продажах мы будем брать из оборотного регистра накопления Продажи, где измерением является Бариста (ссылка на справочник сотрудников), а ресурсом — Стоимость.

Основная логика должна срабатывать в момент интерактивного изменения реквизита Сотрудник в строке табличной части. Нам необходимо выполнить следующие действия:

  1. Определить должность выбранного сотрудника.
  2. Если должность соответствует значению "Бариста", обратиться к регистру накопления.
  3. Получить сумму оборота по ресурсу Стоимость для данного сотрудника за определенный период.
  4. Рассчитать 10% от полученной суммы и подставить результат в поле Премия.
  5. Если должность отличается, установить премию равной нулю.

Реализация обработчика события на форме

Разберем, где именно должен располагаться наш код. Поскольку мы работаем в управляемом приложении, нам важно соблюдать разделение контекста на клиентский и серверный. Основное событие, которое нас интересует, — это СотрудникПриИзменении. Оно создается для элемента формы, связанного с реквизитом табличной части.

Проанализируем пример кода для клиентской части. Здесь мы получаем текущую строку табличной части и вызываем серверную функцию, которая выполнит расчет. Использование серверной функции без контекста (&НаСервереБезКонтекста) является правилом хорошего тона, так как это ускоряет работу системы за счет передачи минимального объема данных.


&НаКлиенте
Процедура НачисленияСотрудникПриИзменении(Элемент)
    
    СтрокаТЧ = Элементы.Начисления.ТекущиеДанные;
    
    Если СтрокаТЧ <> Неопределено Тогда
        // Вызываем функцию расчета на сервере
        РезультатРасчета = РассчитатьПремиюНаСервере(СтрокаТЧ.Сотрудник, СтрокаТЧ.Должность);
        СтрокаТЧ.Премия = РезультатРасчета;
    КонецЕсли;
    
КонецПроцедуры

Разработка серверной логики и работа с запросом

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

Выясним причину, по которой запрос может возвращать пустой результат. Правильный подход заключается в передаче параметров отбора непосредственно в параметры виртуальной таблицы Обороты. Это позволяет платформе оптимизировать запрос на уровне СУБД.

Рассмотрим структуру запроса подробнее:


&НаСервереБезКонтекста
Функция РассчитатьПремиюНаСервере(Знач Сотрудник, Знач ДолжностьСотрудника)
    
    // Проверяем должность. В реальной базе лучше использовать предопределенные 
    // значения или ссылки, а не сравнение строк.
    Если СокрЛП(ДолжностьСотрудника) <> "Бариста" Тогда
        Возврат 0;
    КонецЕсли;

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЕСТЬNULL(ПродажиОбороты.СтоимостьОборот, 0) КАК СтоимостьОборот
        |ИЗ
        |   РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , Бариста = &Бариста) КАК ПродажиОбороты";
    
    // Устанавливаем границы периода (например, текущий месяц)
    Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
    Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДата()));
    Запрос.УстановитьПараметр("Бариста", Сотрудник);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Выборка.Следующий();
        // Рассчитываем 10% от продаж
        Возврат Выборка.СтоимостьОборот * 0.1;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Важные технические нюансы и оптимизация

Проанализируем ключевые моменты в приведенном коде. Во-первых, использование функции ЕСТЬNULL в запросе крайне важно. Если по сотруднику не было движений в регистре, виртуальная таблица может не вернуть строку, или значение ресурса будет NULL. Обработка таких случаев предотвращает ошибки при попытке выполнения арифметических операций.

Во-вторых, обратите внимание на параметры виртуальной таблицы Обороты. Мы передаем туда НачалоПериода и КонецПериода. Если этого не сделать, запрос соберет данные за все время существования базы, что вряд ли корректно для ежемесячного начисления зарплаты. Обычно период берется из даты самого документа.

Особенности работы с должностями: в нашем примере мы передаем Должность напрямую из строки ТЧ. Однако, если должность в справочнике Сотрудники является реквизитом, лучше получать её программно внутри серверной функции, чтобы исключить влияние человеческого фактора (если пользователь вручную изменил должность в строке, но не в карточке сотрудника).

Альтернативный способ через менеджер регистра

Для простых задач 1С предоставляет объектный метод доступа к данным. Рассмотрим, как получить те же данные без написания текста запроса вручную, используя метод Обороты() менеджера регистра накопления.


// Пример использования объектного метода
Отбор = Новый Структура("Бариста", Сотрудник);
ТаблицаОборотов = РегистрыНакопления.Продажи.Обороты(НачалоМесяца, КонецМесяца, Отбор);

Если ТаблицаОборотов.Количество() > 0 Тогда
    СуммаПродаж = ТаблицаОборотов[0].Стоимость;
    Премия = СуммаПродаж * 0.1;
КонецЕсли;

Хотя этот способ кажется более коротким, профессиональные разработчики предпочитают Запрос. Он позволяет более гибко настраивать условия, объединять данные из нескольких таблиц и работает быстрее на больших массивах данных за счет того, что СУБД получает четкую инструкцию по выборке только необходимых полей.

Рекомендации по улучшению кода

При написании дипломных работ или реальных проектов стоит помнить о ссылочной целостности. Сравнение Должность.Наименование = "Бариста" считается плохой практикой ("магические строки"). Правильнее будет создать в конфигурации предопределенный элемент в справочнике должностей или использовать проверку через ссылку:

Если ДолжностьСотрудника = Справочники.Должности.Бариста Тогда...

Также не забывайте про округление. При расчете процентов могут возникнуть длинные дробные части. Используйте функцию Окр(Значение, 2), чтобы сумма премии соответствовала денежному формату.

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

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

← На главную