В практике начинающего разработчика 1С часто встречаются задачи, связанные с обработкой коллекций данных и распределением числовых показателей. Типичный пример — расчет стоимости выполненных работ на основе таблицы трудозатрат и таблицы цен. В этой статье мы подробно разберем, как реализовать такой механизм во внешней обработке, используя наиболее эффективные методы платформы 1С:Предприятие 8.3.
Рассмотрим ситуацию, когда у нас есть две таблицы значений (ТЗ). Первая таблица ТрудозатратыСотрудника содержит информацию о выполненных задачах, их типах и затраченном времени. Вторая таблица ЦеныТиповРабот содержит расценки для каждого типа работ. Наша цель — сопоставить эти данные и рассчитать итоговую сумму по каждой строке.
Для начала проанализируем структуру данных, которую мы будем использовать для заполнения. В программном коде это выглядит следующим образом:
// Заполнение таблицы трудозатрат
ЗаполнитьЗначенияСвойств(ТрудозатратыСотрудника.Добавить(), Новый Структура("ТипРабот,ВидРабот,ЗатраченоЧасов","Поддержка","Задача 25154",8.25));
ЗаполнитьЗначенияСвойств(ТрудозатратыСотрудника.Добавить(), Новый Структура("ТипРабот,ВидРабот,ЗатраченоЧасов","Разработка","Отчет по затратам",10.83));
// Заполнение таблицы цен
ЗаполнитьЗначенияСвойств(ЦеныТиповРабот.Добавить(), Новый Структура("ТипРабот,Цена","Поддержка",133));
ЗаполнитьЗначенияСвойств(ЦеныТиповРабот.Добавить(), Новый Структура("ТипРабот,Цена","Разработка",321));
Основная сложность для новичка заключается в том, как правильно соединить эти две таблицы. Рассмотрим наиболее правильный с точки зрения производительности и стандартов разработки подход — использование механизма запросов.
Проанализируем ситуацию: мы могли бы использовать перебор строк через цикл Для каждого, но это считается плохим тоном (антипаттерном), если данных станет много. Гораздо эффективнее использовать Запрос. Для этого нам понадобится МенеджерВременныхТаблиц.
Разберем по шагам алгоритм реализации:
Запрос и привяжем к нему новый МенеджерВременныхТаблиц.ЛЕВОЕ СОЕДИНЕНИЕ таблицы трудозатрат с таблицей цен по полю ТипРабот.Рассмотрим пример кода, реализующий эту логику:
&НаСервере
Процедура РассчитатьЗПНаСервере()
Запрос = Новый Запрос;
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Передаем таблицы значений в запрос как параметры
Запрос.УстановитьПараметр("ТаблицаТрудозатрат", ТрудозатратыСотрудника.Выгрузить());
Запрос.УстановитьПараметр("ТаблицаЦен", ЦеныТиповРабот.Выгрузить());
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Трудозатраты ИЗ &ТаблицаТрудозатрат КАК ТаблицаТрудозатрат;
|ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Цены ИЗ &ТаблицаЦен КАК ТаблицаЦен;
|
|ВЫБРАТЬ
| ТЗ.ВидРабот КАК ВидРабот,
| ТЗ.ТипРабот КАК ТипРабот,
| ТЗ.ЗатраченоЧасов КАК Часы,
| ЕСТЬNULL(Цены.Цена, 0) КАК Цена,
| ТЗ.ЗатраченоЧасов * ЕСТЬNULL(Цены.Цена, 0) КАК Сумма
|ИЗ
| ВТ_Трудозатраты КАК ТЗ
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цены КАК Цены
| ПО ТЗ.ТипРабот = Цены.ТипРабот";
РезультатРасчета.Загрузить(Запрос.Выполнить().Выгрузить());
КонецПроцедуры
Обратите внимание на использование функции ЕСТЬNULL. Это критически важный момент: если для какого-то типа работ в справочнике цен не окажется записи, запрос вернет NULL, и при умножении на число мы получим ошибку или пустой результат. Использование ЕСТЬNULL(Цены.Цена, 0) гарантирует, что в расчете будет участвовать ноль.
Выясним причину, по которой иногда простого умножения недостаточно. В некоторых тестовых заданиях требуется распределить **фиксированный бюджет** (например, общую сумму ФОТ) пропорционально заработанным баллам или стоимости работ. В этом случае алгоритм усложняется.
Посмотрим на пример, когда нам нужно распределить, скажем, 100 000 рублей между всеми задачами пропорционально их "стоимости" (Часы * Цена).
Для этого нам потребуется сначала вычислить общую "базу" для распределения, а затем рассчитать долю каждой строки. Рассмотрим подробнее код для такого случая:
&НаСервере
Процедура РаспределитьФиксированнуюСумму(ОбщийБюджет)
// 1. Получаем таблицу с предварительным расчетом (как в предыдущем примере)
ТаблицаБазы = РассчитатьПредварительнуюСтоимость();
// 2. Вычисляем общую сумму всех стоимостей
ОбщаяСтоимостьРабот = ТаблицаБазы.Итог("Сумма");
Если ОбщаяСтоимостьРабот = 0 Тогда
Возврат; // Избегаем деления на ноль
КонецЕсли;
// 3. Распределяем бюджет
Для Каждого Строка Из ТаблицаБазы Цикл
Доля = Строка.Сумма / ОбщаяСтоимостьРабот;
Строка.СуммаКВыплате = Окр(Доля * ОбщийБюджет, 2);
КонецЦикла;
// 4. Решаем "проблему копейки"
Разница = ОбщийБюджет - ТаблицаБазы.Итог("СуммаКВыплате");
Если Разница <> 0 И ТаблицаБазы.Количество() > 0 Тогда
// Добавляем разницу к последней строке или строке с наибольшей суммой
ПоследняяСтрока = ТаблицаБазы[ТаблицаБазы.Количество() - 1];
ПоследняяСтрока.СуммаКВыплате = ПоследняяСтрока.СуммаКВыплате + Разница;
КонецЕсли;
КонецПроцедуры
Проанализируем несколько важных моментов, которые отличают профессиональный код от ученического:
Объект.ОписаниеТиповЧисло(15, 2). Это предотвратит потерю точности при расчетах.ТаблицаЗначений существует только на сервере. Если вы работаете в управляемом приложении, передавайте данные с клиента на сервер через ДанныеФормыВЗначение или работайте напрямую с реквизитами формы типа ДанныеФормыКоллекция.Использование Менеджера Временных Таблиц и Запросов позволяет сделать вашу обработку масштабируемой. Даже если количество строк в трудозатратах вырастет до нескольких тысяч, такой код будет работать мгновенно, в отличие от вложенных циклов.
Таким образом, мы разобрали не только базовый расчет "часы на ставку", но и коснулись более сложных вопросов распределения затрат, что часто требуется в реальных бизнес-задачах автоматизации учета.