Расчёт процентов в 1С 8.3: от формулы до отчёта на СКД

· 9 мин

Содержание

Процент в 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.
  • 📉 Путаница «сверху» и «в том числе». Для НДС, скидок с возвратом и комиссий это разные формулы — см. раздел выше.
Проверьте себя
Чему равен НДС 20% «в том числе» в сумме 120 000 рублей?
Что вернёт деление на ноль в языке запросов 1С?
Какой режим `Окр` округляет 2,5 до 3?

Сложные случаи: процент от процента, динамика, взвешенное среднее

Когда базовых формул не хватает.

Процент от процента (сложная наценка). Наценка на наценку считается последовательно, а не сложением ставок. Две наценки по 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
КонецЕсли;

Взвешенное среднее в запросе считается без цикла: СУММА(Цена * Количество) / СУММА(Количество) с защитой знаменателя через ВЫБОР.

Массовые расчёты: не посадить производительность

Процент — операция дешёвая, тормозит обычно не она, а то, как к ней подходят. Три приёма для больших объёмов.

  1. Считайте в запросе, а не в цикле по выборке. Один запрос с арифметикой в полях обрабатывает тысячи строк на сервере. Цикл Для Каждого с обращением к данным внутри — это запрос на каждой итерации.
  2. Не делайте запрос внутри цикла. Если для процента нужна сумма из другого объекта — соберите её одним запросом заранее (во временную таблицу или подзапросом), а не дёргайте базу на каждой строке.
  3. Переносите итог в сам запрос. Общую сумму для доли берите подзапросом или через ИТОГИ ... ОБЩИЕ, а не отдельным обращением к базе после выборки.

Если доля считается от суммы по другому набору данных, соберите эту сумму во временную таблицу один раз и соедините с основным запросом:

ВЫБРАТЬ
    Продажи.Склад КАК Склад,
    СУММА(Продажи.ВыручкаОборот) КАК ВыручкаСклада
ПОМЕСТИТЬ втИтогиПоСкладам
ИЗ
    РегистрНакопления.Продажи.Обороты КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Склад
;
ВЫБРАТЬ
    Строки.Номенклатура КАК Номенклатура,
    Строки.Склад КАК Склад,
    ВЫБОР
        КОГДА Итоги.ВыручкаСклада = 0
            ТОГДА 0
        ИНАЧЕ ВЫРАЗИТЬ(Строки.Выручка
              / Итоги.ВыручкаСклада * 100 КАК ЧИСЛО(15, 2))
    КОНЕЦ КАК ДоляВСкладе
ИЗ
    втДанныеСтрок КАК Строки
        ЛЕВОЕ СОЕДИНЕНИЕ втИтогиПоСкладам КАК Итоги
        ПО Строки.Склад = Итоги.Склад

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

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

На практике расчёт процентов в массовых операциях укладывается в один-два запроса плюс лёгкую постобработку. Если в коде появился вложенный цикл с обращением к базе ради процента — это и есть узкое место, а не формула деления.

Частые вопросы

Почему 1С выдаёт ошибку деления на ноль во встроенном языке, но не в запросе?
Это разные движки. Встроенный язык при делении на ноль прерывает выполнение исключением, а язык запросов возвращает NULL и строку не теряет. Поэтому код из обработки нельзя переносить в запрос без проверки знаменателя через ВЫБОР.
Как округлить процент до двух знаков в запросе, если функции `Окр` там нет?
Используйте ВЫРАЗИТЬ(Значение КАК ЧИСЛО(15, 2)) — приведение к типу с двумя знаками округляет результат, а не отбрасывает хвост.
Почему сумма построчных процентов не сходится с процентом от общей суммы?
Из-за округления каждой строки. Это ожидаемое расхождение на копейки. Если нужен точный итог — считайте процент от общей суммы, а не складывайте округлённые доли строк.
Как в СКД показать долю каждой строки от итога?
В ресурсе или вычисляемом поле делите значение на ВычислитьВыражение("Сумма(Поле)", "ОбщийИтог") и умножаете на 100. Для доли внутри группы вместо "ОбщийИтог" ставьте имя нужной группировки.
Две скидки по 10% — это скидка 20%?
Нет, 19%. Проценты от процентов считаются последовательно: цена умножается на (1 - 0,1) дважды, то есть на 0,81. Складывать ставки нельзя.