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

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

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

Анализ проблемы полного обхода таблицы в цикле

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

Для корректной работы необходимо разделять логику на два независимых уровня:

  1. Пересчет активной (текущей) строки — вычисляется локально при изменении конкретных реквизитов (Количество, Цена, РучнаяСкидка).
  2. Пересчет итоговых показателей документа — агрегирует данные по всей табличной части с использованием оптимизированного платформенного метода Итог().

Шаг 1. Реализация пересчета текущей строки

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

Создадим отдельную процедуру пересчета строки на клиенте:


&НаКлиенте
Процедура ПересчитатьТекущуюСтроку()
    
    СтрокаТаблицы = Элементы.Товары.ТекущиеДанные;
    Если СтрокаТаблицы = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Расчет базовой суммы без скидок
    СтрокаТаблицы.Сумма = Окр(СтрокаТаблицы.Цена * СтрокаТаблицы.Количество, 2);
    
    // Расчет скидок с явным округлением до копеек
    СуммаРучнойСкидки = Окр(СтрокаТаблицы.Сумма * (СтрокаТаблицы.РучнаяСкидка / 100), 2);
    СуммаАвтоСкидки   = Окр(СтрокаТаблицы.Сумма * (СтрокаТаблицы.АвтоматическаяСкидка / 100), 2);
    
    СуммаСкидкиСтроки = СуммаРучнойСкидки + СуммаАвтоСкидки;
    
    // Итоговая сумма по строке с учетом скидки
    СтрокаТаблицы.Всего = СтрокаТаблицы.Сумма - СуммаСкидкиСтроки;
    
    // Вызываем расчет общих итогов документа
    ПересчитатьИтогиДокумента();
    
КонецПроцедуры

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

Шаг 2. Расчет итогов документа через метод Итог()

Для вычисления общих показателей документа в шапке (реквизиты СуммаДокумента или СуммаСкидки) не требуется цикл. Платформа 1С предоставляет быстрый встроенный метод Итог() для табличных частей.


&НаКлиенте
Процедура ПересчитатьИтогиДокумента()
    
    // Итоговая сумма документа по колонке "Всего"
    Объект.СуммаДокумента = Объект.Товары.Итог("Всего");
    
    // Если в строках хранится сумма скидки, ее также можно рассчитать через Итог
    // Объект.СуммаСкидки = Объект.Товары.Итог("СуммаСкидки");
    
    // Либо рассчитываем сумму скидки как разницу базовой суммы и итога
    Объект.СуммаСкидки = Объект.Товары.Итог("Сумма") - Объект.СуммаДокумента;
    
КонецПроцедуры

Важное ограничение: метод Итог("ИмяКолонки") работает только с реквизитами табличной части числового типа. Если промежуточная сумма скидки строки не сохраняется в отдельный реквизит, общий итог скидок рассчитывается через разность сумм колонок Сумма и Всего.

Шаг 3. Подключение обработчиков событий формы

Выясним, к каким событиям формы необходимо привязать процедуры пересчета, чтобы данные всегда оставались актуальными:

  1. События колонок таблицы: для полей ввода ТоварыКоличество, ТоварыЦена, ТоварыРучнаяСкидка назначаем обработчик события ПриИзменении, в котором вызываем ПересчитатьТекущуюСтроку().
  2. Событие таблицы ПриИзменении: срабатывает при добавлении, интерактивном удалении строк или их перемещении. В нем достаточно вызвать ПересчитатьИтогиДокумента(), так как удаление строки меняет только общий итог документа.
  3. Событие таблицы ПриОкончанииРедактирования: позволяет гарантировать корректность данных при завершении ввода новой строки или отмене редактирования.

Посмотрим на пример обработчиков событий в модуле управляемой формы:


&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    ПересчитатьТекущуюСтроку();
КонецПроцедуры

&НаКлиенте
Процедура ТоварыЦенаПриИзменении(Элемент)
    ПересчитатьТекущуюСтроку();
КонецПроцедуры

&НаКлиенте
Процедура ТоварыРучнаяСкидкаПриИзменении(Элемент)
    ПересчитатьТекущуюСтроку();
КонецПроцедуры

&НаКлиенте
Процедура ТоварыПриИзменении(Элемент)
    // При удалении или ручной вставке строки пересчитываем итоги
    ПересчитатьИтогиДокумента();
КонецПроцедуры

Шаг 4. Гарантия целостности данных на сервере (Модуль объекта)

Если документ будет создаваться или перезаполняться программно (например, при обмене данными или выгрузке из внешних источников), события формы вызваны не будут. Поэтому бизнес-логику расчета итогов необходимо дублировать в модуле объекта перед записью.

Откроем модуль объекта документа и добавим обработчик события ПередЗаписью:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Полный пересчет строк для надежности при программной записи
    Для Каждого СтрТовар Из Товары Цикл
        СтрТовар.Сумма = Окр(СтрТовар.Цена * СтрТовар.Количество, 2);
        СуммаСкидки = Окр(СтрТовар.Сумма * ((СтрТовар.РучнаяСкидка + СтрТовар.АвтоматическаяСкидка) / 100), 2);
        СтрТовар.Всего = СтрТовар.Сумма - СуммаСкидки;
    КонецЦикла;
    
    // Обновление реквизитов шапки
    СуммаДокумента = Товары.Итог("Всего");
    СуммаСкидки    = Товары.Итог("Сумма") - СуммаДокумента;
    
КонецПроцедуры

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

← На главную