При разработке документов в 1С часто возникает задача автоматического пересчета сумм. Мы рассмотрим, как корректно рассчитывать сумму для каждой строки табличной части и общую сумму документа, а также как избежать распространенных ошибок, таких как некорректное накопление значений.
Автор столкнулся с проблемой, когда сумма строки рассчитывалась правильно, но общая сумма документа при изменении значений в табличной части некорректно складывала старые значения с новыми. Давайте разберем эту ситуацию подробно и выясним, как правильно организовать логику расчета сумм.
Прежде всего, давайте убедимся, что сумма каждой отдельной строки табличной части рассчитывается корректно. Этот расчет должен происходить при изменении полей, влияющих на сумму строки, обычно это Количество и Цена. Для этого мы будем использовать обработчики событий ПриИзменении для соответствующих элементов формы.
Когда пользователь изменяет значение в колонке табличной части (например, "Количество" или "Цена"), нам нужно пересчитать значение в колонке "Сумма" для текущей строки. Это действие выполняется на клиенте, так как напрямую связано с интерактивным изменением данных в форме.
Рассмотрим пример кода, который необходимо разместить в модуле формы документа:
&НаКлиенте
Процедура УслугиКоличествоПриИзменении(Элемент)
// Получаем текущую строку табличной части, в которой произошло изменение
СтрокаТабличнойЧасти = Элементы.Услуги.ТекущиеДанные;
// Пересчитываем сумму для текущей строки
СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
// После изменения суммы строки, мы можем также обновить общую сумму документа,
// но как мы увидим далее, это не всегда оптимально делать на клиенте при каждом изменении.
// Если требуется немедленное отображение, можно вызвать серверный вызов для пересчета общей суммы.
// ОбновитьОбщуюСуммуДокумента();
КонецПроцедуры
&НаКлиенте
Процедура УслугиЦенаПриИзменении(Элемент)
// Получаем текущую строку табличной части, в которой произошло изменение
СтрокаТабличнойЧасти = Элементы.Услуги.ТекущиеДанные;
// Пересчитываем сумму для текущей строки
СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
// Аналогично, если требуется немедленное отображение общей суммы:
// ОбновитьОбщуюСуммуДокумента();
КонецПроцедуры
В этом коде Элементы.Услуги.ТекущиеДанные возвращает объект, представляющий текущую строку табличной части, отображаемую на форме. Через этот объект мы можем получить доступ к значениям колонок этой строки (СтрокаТабличнойЧасти.Количество, СтрокаТабличнойЧасти.Цена) и присвоить новое значение колонке СтрокаТабличнойЧасти.Сумма.
Основная проблема, с которой столкнулся автор, заключалась в некорректном расчете общей суммы документа. Первоначальная попытка выглядела так:
СтрокаТабличнойЧасти = Элементы.ТекущиеДанные;
// ...
Объект.СуммаДокумента = СтрокаТабличнойЧасти.Итог("Сумма");
Здесь кроется ключевая ошибка: переменная СтрокаТабличнойЧасти, как следует из её названия и из логики пересчета суммы строки, представляет собой одну строку табличной части, а не всю табличную часть целиком. Метод Итог("Сумма") предназначен для подсчета суммы по определенной колонке для всей табличной части, а не для отдельной строки.
Для получения общей суммы по колонке "Сумма" во всей табличной части документа, необходимо вызвать метод Итог() для самого реквизита табличной части объекта документа. Этот реквизит доступен через основной реквизит формы Объект.
Корректный код для расчета общей суммы документа будет выглядеть следующим образом:
Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма");
Здесь Объект.Услуги — это коллекция строк табличной части "Услуги" документа. Вызов .Итог("Сумма") на этой коллекции вернет суммарное значение по колонке "Сумма" для всех строк табличной части. Метод Итог() работает с числовыми колонками и возвращает сумму их значений.
Даже после исправления синтаксиса на Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма"); автор продолжал сообщать, что "Прошлые значения и значения после изменения Количества или Цены складываются друг с другом". Это очень важный момент, так как оператор присваивания = должен перезаписывать значение, а не складывать его. Если происходит накопление, это указывает на другую проблему. Давайте проанализируем возможные причины и методы диагностики:
Объект.СуммаДокумента, но делает это путем сложения (Объект.СуммаДокумента = Объект.СуммаДокумента + ...) вместо прямого присваивания полной суммы.
СуммаДокумента. Особенно обратите внимание на события ПриИзменении, ПослеЗаписиНаСервере, ПередЗаписью, ПриЗагрузкеДанныхНаСервере и любые другие процедуры, которые вызываются после изменения табличной части.Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма");. Запустите 1С в режиме отладки, измените количество или цену в табличной части и дождитесь срабатывания точки останова.
Объект.СуммаДокумента *перед* выполнением этой строки.Объект.СуммаДокумента *после* выполнения этой строки.
Если после выполнения этой строки значение корректно, а позже оно снова накапливается, это означает, что есть другой участок кода, который его меняет. Продолжайте отладку по шагам, чтобы отследить, какой именно код выполняет нежелательное сложение.
Важно: Метод Итог("Сумма") всегда возвращает текущую сумму по колонке. Он не "помнит" предыдущие вызовы и не складывает результаты. Если происходит накопление, то ошибка не в работе Итог(), а в том, как вы обрабатываете полученное значение.
Хотя технически возможно пересчитывать общую сумму документа на клиенте при каждом изменении строки, это редко является оптимальным решением и чаще всего считается "плохой практикой" (как было отмечено в сообщении 19). Почему?
В большинстве типовых конфигураций 1С расчет общей суммы документа выполняется один раз перед его записью в базу данных. Это гарантирует, что сумма всегда будет актуальной и корректной в сохраненном документе. Для этого используется процедура ПередЗаписью в модуле объекта документа. Эта процедура выполняется на сервере.
Рассмотрим пример кода для модуля объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, не происходит ли загрузка данных через обмен.
// Если да, то расчет суммы может быть избыточным или нарушить логику обмена.
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Пересчитываем общую сумму документа, используя метод Итог() табличной части.
// Это гарантирует, что сумма будет актуальной перед сохранением.
СуммаДокумента = Услуги.Итог("Сумма");
// Здесь же могут быть выполнены и другие проверки или заполнения реквизитов.
// Например, расчет НДС, если он не рассчитывается построчно,
// или проверка заполнения обязательных полей.
КонецПроцедуры
Преимущества такого подхода:
Важно понимать различие между клиентским и серверным кодом в управляемых формах 1С.
&НаКлиенте означает, что код выполняется на стороне клиентского приложения (тонкий клиент, веб-клиент). Обработчики событий элементов формы, такие как ПриИзменении, обычно выполняются на клиенте для обеспечения быстрой реакции интерфейса.&НаСервере выполняется на сервере 1С:Предприятия. Работа с данными объекта документа (например, Объект.Услуги.Итог("Сумма"), если объект еще не записан и данные нужно получить из текущих строк формы) часто требует серверного контекста. Процедура ПередЗаписью модуля объекта всегда выполняется на сервере.Если вам все же необходимо видеть актуальную общую сумму документа на форме до его записи, и при этом не хотите делать прямые пересчеты на клиенте, у нас есть несколько вариантов:
Объект.Услуги.Итог("Сумма") и возвращать результат на клиент. Эту функцию можно вызывать после каждого изменения строки, но с осторожностью, чтобы не создать чрезмерную нагрузку на сервер.
&НаКлиенте
Процедура ОбновитьСуммуДокумента()
Объект.СуммаДокумента = ПолучитьСуммуДокументаНаСервере();
КонецПроцедуры
&НаСервере
Функция ПолучитьСуммуДокументаНаСервере()
Возврат Объект.Услуги.Итог("Сумма");
КонецФункции
И вызывать ОбновитьСуммуДокумента() в конце обработчиков ПриИзменении для полей "Количество" и "Цена".
1С автоматически будет рассчитывать и отображать сумму в подвале, что является удобным для пользователя и не требует написания дополнительного кода для интерактивного пересчета на форме. При необходимости более гибкого отображения показателей с учетом отборов можно использовать готовый набор процедур для организации итогов в подвале табличных частей управляемых форм.
СуммаДокумента основного объекта или к вспомогательному реквизиту формы. В этом случае, если вы не используете подход с подвалом, вам придется вручную обновлять это поле, вызывая серверную функцию, как описано выше.Наконец, давайте еще раз подчеркнем различие между Элементы.ТабличнаяЧасть.ТекущиеДанные и Объект.ТабличнаяЧасть:
Элементы.ТабличнаяЧасть.ТекущиеДанные относится к данным текущей строки, отображаемой в табличном поле формы. Это интерфейсный объект, используемый для изменения значений в полях текущей строки.Объект.ТабличнаяЧасть (например, Объект.Услуги) — это коллекция строк табличной части, представляющая все данные табличной части документа, как они хранятся в объекте. К ней применяется метод Итог() для подсчета сумм по колонкам.Эти два подхода (Элементы для текущей строки и Объект для всей табличной части) используются для разных целей и не должны смешиваться в логике получения итогов по всей таблице.
Итак, для того чтобы успешно решить проблему с расчетом общей суммы документа, мы рекомендуем следующие шаги:
СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;) выполняется в обработчиках ПриИзменении для полей "Количество" и "Цена" в модуле формы.Объект.СуммаДокумента = Объект.Услуги.Итог("Сумма");) перенесите в процедуру ПередЗаписью модуля объекта документа.Объект.СуммаДокумента, и скорректируйте их логику.Следуя этим рекомендациям, вы сможете построить надежную и производительную систему расчета сумм в документах 1С.