Расчёт процентов в 1С 8.3: от формулы до отчёта на СКД
Содержание
Процент в 1С считается одной строкой: Результат = Сумма * Ставка / 100. Долю от целого — наоборот: Доля = Часть / Целое * 100. Всё остальное — это те же две формулы плюс аккуратность с округлением и делением на ноль.
Сложности начинаются не в арифметике, а в том, где вы считаете. На встроенном языке деление на ноль вызывает ошибку, а в запросе молча возвращает NULL. В отчёте на СКД процент к итогу берётся не из строки, а из группировки. Ниже — рабочий код для каждого из этих мест и разбор, почему один и тот же расчёт в разных модулях ведёт себя по-разному.
Базовые формулы процента на встроенном языке
Два направления расчёта, которые закрывают 90% задач.
Процент от суммы (НДФЛ, скидка, наценка, комиссия):
Результат = Сумма * Ставка / 100;
// 15000 * 13 / 100 = 1950
Доля в процентах (сколько процентов одно число составляет от другого):
Доля = Часть / Целое * 100;
// 2500 / 10000 * 100 = 25
Обе строки надо защитить от двух проблем: деления на ноль и «хвоста» после запятой.
Деление на ноль и округление
Деление на ноль на встроенном языке 1С — это не NULL, а исключение с прерыванием выполнения. Поэтому знаменатель всегда проверяют:
Функция ДоляВПроцентах(Часть, Целое) Экспорт
Если Целое = 0 Тогда
Возврат 0;
КонецЕсли;
Возврат Окр(Часть / Целое * 100, 2);
КонецФункции
За округление отвечает функция Окр(Число, Разрядность, РежимОкругления):
Окр(1950.555, 2)→1950.56— два знака после запятой;- режим округления по умолчанию (1) округляет 2,5 до 3, а режим
0— до 2.
Если суммируете несколько округлённых процентов и итог «не бьётся» на копейку — виноват как раз режим округления. Договоритесь в команде об одном режиме и вынесите расчёт в общий модуль, чтобы не плодить варианты.
Для процента от суммы функция симметрична:
Функция ПроцентОтСуммы(Сумма, Ставка) Экспорт
Возврат Окр(Сумма * Ставка / 100, 2);
КонецФункции
Проценты и доли в запросах 1С
В языке запросов арифметика работает прямо в полях выборки, но правила другие.
Главное отличие: деление на ноль в запросе не прерывает выполнение, а возвращает NULL. Строка с таким значением не «упадёт», но и процента в ней не будет. Поэтому знаменатель закрывают конструкцией ВЫБОР:
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
ВЫБОР
КОГДА Товары.Себестоимость = 0
ТОГДА 0
ИНАЧЕ (Товары.Цена - Товары.Себестоимость)
/ Товары.Себестоимость * 100
КОНЕЦ КАК НаценкаПроцент
ИЗ
Справочник.Номенклатура КАК Товары
Округление результата в запросе
Отдельной функции округления в языке запросов нет. Привести число к нужной разрядности помогает ВЫРАЗИТЬ:
ВЫРАЗИТЬ(Товары.Цена / Товары.Себестоимость * 100 КАК ЧИСЛО(15, 2))
ЧИСЛО(15, 2) — 15 знаков всего, 2 после запятой. Значение при этом округляется, а не обрезается.
Не путайте NULL и ноль. Если в поле попал NULL (например, из-за левого соединения), дальнейшая арифметика с ним снова даст NULL. Оборачивайте такие поля в ЕСТЬNULL(Поле, 0) до того, как делите или умножаете.
Процент к общему итогу в самом запросе
Долю строки от всей выборки удобно посчитать подзапросом, который возвращает общую сумму:
ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура,
Продажи.Выручка КАК Выручка,
ВЫБОР
КОГДА Итоги.ВсегоВыручка = 0
ТОГДА 0
ИНАЧЕ ВЫРАЗИТЬ(Продажи.Выручка
/ Итоги.ВсегоВыручка * 100 КАК ЧИСЛО(15, 2))
КОНЕЦ КАК ДоляПроцент
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи,
(ВЫБРАТЬ
СУММА(Об.ВыручкаОборот) КАК ВсегоВыручка
ИЗ
РегистрНакопления.Продажи.Обороты КАК Об) КАК Итоги
Подзапрос Итоги без условий связи даёт одну строку с общей суммой — она подставляется в каждую строку основного запроса.
Расчёт процентов в отчётах на СКД
В системе компоновки данных процент считают не в коде, а в выражениях полей. Два рабочих способа.
Вычисляемое поле. На вкладке «Вычисляемые поля» добавляете поле, например НаценкаПроцент, и в выражении пишете ту же формулу встроенного языка:
Выбор
Когда Себестоимость = 0 Тогда 0
Иначе (Цена - Себестоимость) / Себестоимость * 100
Конец
Процент к итогу группировки. Это типовая задача: «сколько процентов даёт каждый товар от итога по складу». Здесь нужна функция ВычислитьВыражение, которая берёт значение из другой группировки:
Выручка / ВычислитьВыражение("Сумма(Выручка)", "ОбщийИтог") * 100
- второй параметр
"ОбщийИтог"— область расчёта знаменателя; - для доли внутри своей группы вместо
"ОбщийИтог"ставят имя родительской группировки.
Выражение процента удобнее задавать не в вычисляемом поле, а в ресурсе — тогда СКД сам пересчитает его на каждом уровне группировки, от строки до общего итога.
Разрядность вывода настраивается в поле на вкладке «Наборы данных» или в условном оформлении: формат ЧФ=0; ЧДЦ=2 покажет два знака после запятой.
Проценты в документах: НДС, скидки, наценки
В документах процент почти всегда считается в модуле объекта при проведении или в обработчике изменения реквизита табличной части.
Наценка и скидка в строке. При изменении цены или процента скидки пересчитывают сумму строки:
СтрокаТЧ.СуммаСкидки = Окр(СтрокаТЧ.Сумма * СтрокаТЧ.ПроцентСкидки / 100, 2);
СтрокаТЧ.СуммаКОплате = СтрокаТЧ.Сумма - СтрокаТЧ.СуммаСкидки;
НДС сверху (начислить на сумму без налога):
СуммаНДС = Окр(СуммаБезНДС * СтавкаНДС / 100, 2);
// 100000 * 20 / 100 = 20000
НДС в том числе (выделить налог из суммы с НДС) — здесь частая арифметическая ошибка. Нельзя просто взять 20% от суммы с налогом, нужна формула с суммой ставки и ста:
СуммаНДС = Окр(СуммаСНДС * СтавкаНДС / (100 + СтавкаНДС), 2);
// 120000 * 20 / 120 = 20000
Сравните: 20% «сверху» от 100 000 и 20% «в том числе» из 120 000 дают один и тот же налог — 20 000 рублей. А вот 20% от 120 000 (24 000) — это уже ошибка.
Расчёт процента в табличной части вешайте на обработчик ПриИзменении реквизита, а не только на проведение. Иначе пользователь увидит старую сумму до записи документа.
Ошибки, из-за которых процент считается неверно
Самые частые причины, по которым «1С считает не тот процент».
- 🔢 Целочисленное деление в чужом коде. Во встроенном языке 1С деление всегда вещественное,
1 / 3даст0.333.... Но если число пришло строкой и не было приведено черезЧисло(), конкатенация вместо сложения даст мусор. - ➗ Деление на ноль по-разному в разных местах. В модуле — исключение и обрыв, в запросе —
NULL. Код, перенесённый из обработки в запрос без правки, «тихо» теряет строки. - 🔁 Потеря копеек при суммировании. Округлили каждую строку, сложили — итог не сошёлся с округлением общей суммы. Это нормальное поведение, а не баг; решается расчётом процента от итога, а не суммой построчных процентов.
- 🧮
NULLв знаменателе или числителе. После левого соединения поле пустое, и весь расчёт обнуляется вNULL. ЛечитсяЕСТЬNULL. - 📉 Путаница «сверху» и «в том числе». Для НДС, скидок с возвратом и комиссий это разные формулы — см. раздел выше.
Сложные случаи: процент от процента, динамика, взвешенное среднее
Когда базовых формул не хватает.
Процент от процента (сложная наценка). Наценка на наценку считается последовательно, а не сложением ставок. Две наценки по 10% дают не 20%, а 21%:
ИтогЦена = БазоваяЦена * (1 + Наценка1 / 100) * (1 + Наценка2 / 100);
// 100 * 1.1 * 1.1 = 121 → наценка 21%, а не 20%
Динамика (рост или падение к прошлому периоду). Классическая формула темпа прироста:
Если ПрошлыйПериод = 0 Тогда
ДинамикаПроцент = 0; // роста «от нуля» в процентах не бывает
Иначе
ДинамикаПроцент = Окр((ТекущийПериод - ПрошлыйПериод)
/ ПрошлыйПериод * 100, 1);
КонецЕсли;
// (120 - 100) / 100 * 100 = 20 → рост на 20%
Отрицательный результат означает падение — отдельно его обрабатывать не нужно, знак получается сам.
Взвешенное среднее (средняя цена, средняя ставка). Простое среднее здесь врёт: если 2 штуки по 100 рублей и 3 штуки по 200, средняя цена не 150, а 160. Нужно взвесить по количеству:
СуммаПроизведений = 0;
СуммаВесов = 0;
Для Каждого Строка Из ТаблицаТоваров Цикл
СуммаПроизведений = СуммаПроизведений + Строка.Цена * Строка.Количество;
СуммаВесов = СуммаВесов + Строка.Количество;
КонецЦикла;
Если СуммаВесов <> 0 Тогда
СредняяЦена = Окр(СуммаПроизведений / СуммаВесов, 2);
// (100*2 + 200*3) / (2 + 3) = 800 / 5 = 160
КонецЕсли;
Взвешенное среднее в запросе считается без цикла: СУММА(Цена * Количество) / СУММА(Количество) с защитой знаменателя через ВЫБОР.
Массовые расчёты: не посадить производительность
Процент — операция дешёвая, тормозит обычно не она, а то, как к ней подходят. Три приёма для больших объёмов.
- Считайте в запросе, а не в цикле по выборке. Один запрос с арифметикой в полях обрабатывает тысячи строк на сервере. Цикл
Для Каждогос обращением к данным внутри — это запрос на каждой итерации. - Не делайте запрос внутри цикла. Если для процента нужна сумма из другого объекта — соберите её одним запросом заранее (во временную таблицу или подзапросом), а не дёргайте базу на каждой строке.
- Переносите итог в сам запрос. Общую сумму для доли берите подзапросом или через
ИТОГИ ... ОБЩИЕ, а не отдельным обращением к базе после выборки.
Если доля считается от суммы по другому набору данных, соберите эту сумму во временную таблицу один раз и соедините с основным запросом:
ВЫБРАТЬ
Продажи.Склад КАК Склад,
СУММА(Продажи.ВыручкаОборот) КАК ВыручкаСклада
ПОМЕСТИТЬ втИтогиПоСкладам
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.Склад
;
ВЫБРАТЬ
Строки.Номенклатура КАК Номенклатура,
Строки.Склад КАК Склад,
ВЫБОР
КОГДА Итоги.ВыручкаСклада = 0
ТОГДА 0
ИНАЧЕ ВЫРАЗИТЬ(Строки.Выручка
/ Итоги.ВыручкаСклада * 100 КАК ЧИСЛО(15, 2))
КОНЕЦ КАК ДоляВСкладе
ИЗ
втДанныеСтрок КАК Строки
ЛЕВОЕ СОЕДИНЕНИЕ втИтогиПоСкладам КАК Итоги
ПО Строки.Склад = Итоги.Склад
Итог по складу считается один раз на всю таблицу, а не на каждой строке — на больших оборотах разница в разы.
Прежде чем оптимизировать, замерьте. Включите в конфигураторе замер производительности или журнал регистрации с длительностью запросов — часто «тормозит процент», а на деле медленный сам источник данных: обороты по регистру за весь период без отбора.
На практике расчёт процентов в массовых операциях укладывается в один-два запроса плюс лёгкую постобработку. Если в коде появился вложенный цикл с обращением к базе ради процента — это и есть узкое место, а не формула деления.
Частые вопросы
Почему 1С выдаёт ошибку деления на ноль во встроенном языке, но не в запросе?
NULL и строку не теряет. Поэтому код из обработки нельзя переносить в запрос без проверки знаменателя через ВЫБОР.Как округлить процент до двух знаков в запросе, если функции `Окр` там нет?
ВЫРАЗИТЬ(Значение КАК ЧИСЛО(15, 2)) — приведение к типу с двумя знаками округляет результат, а не отбрасывает хвост.Почему сумма построчных процентов не сходится с процентом от общей суммы?
Как в СКД показать долю каждой строки от итога?
ВычислитьВыражение("Сумма(Поле)", "ОбщийИтог") и умножаете на 100. Для доли внутри группы вместо "ОбщийИтог" ставьте имя нужной группировки.Две скидки по 10% — это скидка 20%?
(1 - 0,1) дважды, то есть на 0,81. Складывать ставки нельзя.