Как правильно рассчитать общую сумму документа и сумму строки табличной части в 1С?

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

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

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

1. Расчет суммы в строке табличной части

Прежде всего, давайте убедимся, что сумма каждой отдельной строки табличной части рассчитывается корректно. Этот расчет должен происходить при изменении полей, влияющих на сумму строки, обычно это Количество и Цена. Для этого мы будем использовать обработчики событий ПриИзменении для соответствующих элементов формы.

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

Рассмотрим пример кода, который необходимо разместить в модуле формы документа:


&НаКлиенте
Процедура УслугиКоличествоПриИзменении(Элемент)
    // Получаем текущую строку табличной части, в которой произошло изменение
    СтрокаТабличнойЧасти = Элементы.Услуги.ТекущиеДанные;
    
    // Пересчитываем сумму для текущей строки
    СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
    
    // После изменения суммы строки, мы можем также обновить общую сумму документа,
    // но как мы увидим далее, это не всегда оптимально делать на клиенте при каждом изменении.
    // Если требуется немедленное отображение, можно вызвать серверный вызов для пересчета общей суммы.
    // ОбновитьОбщуюСуммуДокумента(); 
КонецПроцедуры

&НаКлиенте
Процедура УслугиЦенаПриИзменении(Элемент)
    // Получаем текущую строку табличной части, в которой произошло изменение
    СтрокаТабличнойЧасти = Элементы.Услуги.ТекущиеДанные;
    
    // Пересчитываем сумму для текущей строки
    СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
    
    // Аналогично, если требуется немедленное отображение общей суммы:
    // ОбновитьОбщуюСуммуДокумента();
КонецПроцедуры

В этом коде Элементы.Услуги.ТекущиеДанные возвращает объект, представляющий текущую строку табличной части, отображаемую на форме. Через этот объект мы можем получить доступ к значениям колонок этой строки (СтрокаТабличнойЧасти.Количество, СтрокаТабличнойЧасти.Цена) и присвоить новое значение колонке СтрокаТабличнойЧасти.Сумма.

2. Расчет общей суммы документа (суммы по табличной части)

Основная проблема, с которой столкнулся автор, заключалась в некорректном расчете общей суммы документа. Первоначальная попытка выглядела так:


СтрокаТабличнойЧасти = Элементы.ТекущиеДанные;
// ...
Объект.СуммаДокумента = СтрокаТабличнойЧасти.Итог("Сумма");

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

Для получения общей суммы по колонке "Сумма" во всей табличной части документа, необходимо вызвать метод Итог() для самого реквизита табличной части объекта документа. Этот реквизит доступен через основной реквизит формы Объект.

Корректный код для расчета общей суммы документа будет выглядеть следующим образом:

Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма");

Здесь Объект.Услуги — это коллекция строк табличной части "Услуги" документа. Вызов .Итог("Сумма") на этой коллекции вернет суммарное значение по колонке "Сумма" для всех строк табличной части. Метод Итог() работает с числовыми колонками и возвращает сумму их значений.

3. Причина некорректного накопления суммы: где искать ошибку?

Даже после исправления синтаксиса на Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма"); автор продолжал сообщать, что "Прошлые значения и значения после изменения Количества или Цены складываются друг с другом". Это очень важный момент, так как оператор присваивания = должен перезаписывать значение, а не складывать его. Если происходит накопление, это указывает на другую проблему. Давайте проанализируем возможные причины и методы диагностики:

  1. Другие обработчики событий: Вполне возможно, что существует другой обработчик события, который также модифицирует реквизит Объект.СуммаДокумента, но делает это путем сложения (Объект.СуммаДокумента = Объект.СуммаДокумента + ...) вместо прямого присваивания полной суммы.
    • Что проверить: Внимательно изучите модуль формы документа и модуль объекта на предмет других процедур, которые обращаются к реквизиту СуммаДокумента. Особенно обратите внимание на события ПриИзменении, ПослеЗаписиНаСервере, ПередЗаписью, ПриЗагрузкеДанныхНаСервере и любые другие процедуры, которые вызываются после изменения табличной части.
  2. Неверное понимание результата: Иногда пользователь может ошибочно интерпретировать поведение. Однако, если значение действительно постоянно увеличивается, а не устанавливается в корректный итог, то это почти наверняка связано с некорректной логикой в коде.
  3. Отладка: Самый эффективный способ выяснить причину — использовать отладчик.
    • Как отлаживать: Установите точку останова непосредственно на строке Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма");. Запустите 1С в режиме отладки, измените количество или цену в табличной части и дождитесь срабатывания точки останова.
      • Шаг 1: Посмотрите значение Объект.СуммаДокумента *перед* выполнением этой строки.
      • Шаг 2: Выполните строку (шагнуть через F10).
      • Шаг 3: Посмотрите значение Объект.СуммаДокумента *после* выполнения этой строки.

        Если после выполнения этой строки значение корректно, а позже оно снова накапливается, это означает, что есть другой участок кода, который его меняет. Продолжайте отладку по шагам, чтобы отследить, какой именно код выполняет нежелательное сложение.

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

4. Оптимальное место для расчета общей суммы документа

Хотя технически возможно пересчитывать общую сумму документа на клиенте при каждом изменении строки, это редко является оптимальным решением и чаще всего считается "плохой практикой" (как было отмечено в сообщении 19). Почему?

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

Рассмотрим пример кода для модуля объекта:


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

Преимущества такого подхода:

  1. Один источник истины: Сумма документа всегда рассчитывается в одном, контролируемом месте перед записью.
  2. Серверный контекст: Расчеты на сервере более надежны, так как имеют полный доступ к данным объекта и конфигурации.
  3. Оптимизация: Избегаем ненужных вычислений на клиенте, улучшая отзывчивость формы.
  4. Корректность при программном создании: Если документ создается или изменяется программно, а не интерактивно через форму, сумма также будет корректно рассчитана перед записью.

5. Разделение клиентского и серверного кода и отображение итогов

Важно понимать различие между клиентским и серверным кодом в управляемых формах 1С.

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

  1. Вызов серверной функции для обновления: Вы можете создать серверную функцию в модуле формы, которая будет вызывать Объект.Услуги.Итог("Сумма") и возвращать результат на клиент. Эту функцию можно вызывать после каждого изменения строки, но с осторожностью, чтобы не создать чрезмерную нагрузку на сервер.
    
    &НаКлиенте
    Процедура ОбновитьСуммуДокумента()
        Объект.СуммаДокумента = ПолучитьСуммуДокументаНаСервере();
    КонецПроцедуры
            
    &НаСервере
    Функция ПолучитьСуммуДокументаНаСервере()
        Возврат Объект.Услуги.Итог("Сумма");
    КонецФункции
    

    И вызывать ОбновитьСуммуДокумента() в конце обработчиков ПриИзменении для полей "Количество" и "Цена".

  2. Отображение итогов в подвале табличной части: Самый простой и часто используемый способ – это настроить отображение итогов непосредственно в подвале табличной части на форме.
    1. Откройте форму документа в конфигураторе.
    2. Выберите элемент формы, представляющий табличную часть ("Услуги" в нашем случае).
    3. В окне свойств элемента установите флажок "Подвал".
    4. Перейдите на вкладку "Колонки" табличной части, выберите колонку "Сумма" и в ее свойствах установите флажок "Итог".

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

  3. Отдельные поля для итогов: Можно вывести итоговую сумму в отдельное поле на форме (например, в группе "Итоги"). Это поле может быть привязано к реквизиту СуммаДокумента основного объекта или к вспомогательному реквизиту формы. В этом случае, если вы не используете подход с подвалом, вам придется вручную обновлять это поле, вызывая серверную функцию, как описано выше.

Наконец, давайте еще раз подчеркнем различие между Элементы.ТабличнаяЧасть.ТекущиеДанные и Объект.ТабличнаяЧасть:

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

Итак, для того чтобы успешно решить проблему с расчетом общей суммы документа, мы рекомендуем следующие шаги:

  1. Убедитесь, что расчет суммы строки (СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;) выполняется в обработчиках ПриИзменении для полей "Количество" и "Цена" в модуле формы.
  2. Основной расчет общей суммы документа (Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма");) перенесите в процедуру ПередЗаписью модуля объекта документа.
  3. Если наблюдается некорректное накопление суммы, используйте отладчик для выявления других участков кода, которые могут модифицировать реквизит Объект.СуммаДокумента, и скорректируйте их логику.
  4. Для отображения итогов на форме рассмотрите использование подвала табличной части или вызов серверной функции для обновления реквизита формы, отображающего общую сумму.

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

← На главную