Как пересчитать сумму документа после программного изменения табличной части во внешней обработке 1С 8.3

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

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

В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем архитектурные особенности платформы 1С:Предприятие 8.3 и рассмотрим два классических сценария решения: фоновое изменение документов в базе данных (пакетный режим) и интерактивное заполнение данных прямо в открытой форме пользователя.

Прежде чем перейти к коду, давайте выясним причину такого поведения системы. В управляемом приложении данные на клиенте (в форме) и данные на сервере (в базе данных) жестко разделены. Когда пользователь меняет цену вручную, платформа запускает цепочку интерактивных обработчиков событий (таких как ПриИзменении элементов формы), которые обращаются к общим модулям конфигурации для автоматического пересчета строк и итогов шапки. Когда же мы меняем данные программно во внешней обработке, эти визуальные обработчики формы не срабатывают автоматически. Соответственно, пересчет необходимо инициировать самостоятельно.

Рассмотрим подробно оба сценария реализации.

Сценарий 1: Пакетный пересчет документов в базе данных (на сервере)

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

Критическая ошибка многих новичков — пытаться рассчитать значения колонок простым перемножением, например: Строка.Сумма = Строка.Количество * Строка.Цена. Это в корне неверно! В современных конфигурациях расчет строки зависит от множества факторов: включен ли НДС в стоимость, рассчитывается ли автоматическая или ручная скидка, какая валюта используется в документе. Для правильного расчета необходимо использовать штатные функции конфигурации, расположенные в общих модулях.

Давайте разберем правильный алгоритм пакетного пересчета на сервере шаг за шагом:

  1. Получаем объект документа из ссылки с помощью метода ПолучитьОбъект().
  2. Временно отменяем проведение (если документ был проведен), чтобы избежать блокировок и ошибок контроля остатков при перезаписи.
  3. Подготавливаем специальную структуру параметров расчета, которая определяет правила вычисления налогов и скидок.
  4. В цикле обходим строки табличной части, меняем нужные реквизиты и передаем строку в процедуру общего модуля для авторасчета зависимых полей.
  5. Пересчитываем итоговые реквизиты шапки документа (например, СуммаДокумента).
  6. Записываем объект в базу данных в режиме записи или проведения.

Посмотрим на пример реализации данного алгоритма для конфигурации 1С:УНФ:


Процедура ПересчитатьДокументыНаСервере(МассивСсылокДокументов, НоваяЦена) Экспорт

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

КонецПроцедуры

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

Сценарий 2: Пересчет данных в открытой интерактивной форме (через БСП)

Второй сценарий гораздо более распространен при создании пользовательских инструментов. Пользователь открывает форму документа «Расходная накладная», нажимает кнопку «Заполнить» (которая вызывает нашу внешнюю обработку), данные в табличной части изменяются, и пользователь должен сразу увидеть новые цены, суммы и итоговую сумму документа на своем экране. При этом документ **не должен** автоматически записываться в базу данных — пользователь должен сам решить, сохранить изменения или закрыть документ без записи.

Для реализации этого сценария в рамках библиотеки стандартных подсистем (БСП) используется механизм заполнения объектов. В параметрах выполнения команды обработки нам передается контекст активной управляемой формы. Наша главная задача — изменить данные на форме так, чтобы платформа сама поняла, что данные изменились, и перерисовала все зависимые элементы интерфейса.

Для корректного преобразования данных формы в прикладной объект и обратно используется связка функций ДанныеФормыВЗначение и ЗначениеВРеквизитФормы. Рассмотрим этот изящный алгоритм:


// Процедура вызывается из механизма БСП "Дополнительные отчеты и обработки"
Процедура ВыполнитьКоманду(ИмяКоманды, ОбъектыНазначения, ПараметрыВыполнения) Экспорт

    // Получаем контекст открытой управляемой формы документа
    ЭтаФорма = ПараметрыВыполнения.ЭтаФорма;
    
    // Преобразуем структуру "ДанныеФормы" в реальный объект документа на сервере
    ДокОбъект = ДанныеФормыВЗначение(ЭтаФорма.Объект, Тип("ДокументОбъект.РасходнаяНакладная"));
    
    // Готовим параметры пересчета в зависимости от настроек документа
    ПараметрыРасчета = Новый Структура("СуммаВключаетНДС", ДокОбъект.СуммаВключаетНДС);
    
    // Модифицируем табличную часть
    Для Каждого СтрокаТЧ Из ДокОбъект.Запасы Цикл
        
        // Например, увеличиваем цену на 10%
        СтрокаТЧ.Цена = СтрокаТЧ.Price * 1.1; 
        
        // Пересчитываем строку ТЧ типовыми функциями конфигурации
        УправлениеНебольшойФирмойКлиентСервер.РассчитатьСуммыВСтрокеТЧ(СтрокаТЧ, ПараметрыРасчета);
        
    КонецЦикла;
    
    // Пересчитываем итоги в шапке объекта
    ДокОбъект.СуммаДокумента = ДокОбъект.Запасы.Итог("Сумма") 
        + ДокОбъект.СтоимостьДоставки 
        + ?(ДокОбъект.СуммаВключаетНДС, 0, ДокОбъект.Запасы.Итог("СуммаНДС") + ДокОбъект.СуммаНДСДоставки);
    
    // Возвращаем измененный объект обратно в структуру данных управляемой формы
    ЗначениеВРеквизитФормы(ДокОбъект, "Объект");
    
    // Обязательно взводим флаг модифицированности формы!
    // Это позволит пользователю увидеть, что данные изменились (в заголовке формы появится звездочка)
    // и система предложит сохранить изменения при попытке закрытия окна.
    ЭтаФорма.Модифицированность = Истина;

КонецПроцедуры

Благодаря вызову метода ЗначениеВРеквизитФормы все изменения, сделанные нами в объекте ДокОбъект на сервере, автоматически спроецируются на элементы интерфейса. Все итоговые подвалы табличных частей, индикаторы сумм и НДС на форме обновятся без единого вызова записи в базу данных.

Типичные ошибки разработчиков при пересчете сумм

Проанализировав переписку участников на форуме, мы выделили топ-3 критических ошибок, которые совершают начинающие программисты 1С при попытке решить эту задачу:

  1. Использование устаревшего метода ПолучитьФорму() внутри серверных циклов. Как показано в сообщении 24, попытка вызвать метод открытия формы внутри серверного цикла по обработке объектов приводит к ошибке выполнения. Получение или открытие форм — это строго клиентские операции. Пытаться открыть форму документа в фоновом режиме записи документов бессмысленно и вредно для производительности системы.
  2. Попытка использовать ОбновитьОтображениеДанных() вместо реального обновления данных. Некоторые разработчики полагают, что метод формы ОбновитьОтображениеДанных() умеет производить расчеты математики документа. Это не так. Этот метод отвечает исключительно за перерисовку визуального интерфейса. Если в самих структурах данных (в реквизите формы Объект) значения сумм остались старыми, то визуальное обновление формы ничего не изменит.
  3. Копирование процедур модуля формы в модуль объекта с сохранением клиентских директив. Часто разработчики пытаются скопировать интерактивные процедуры формы (например, ПриИзмененииНоменклатуры) в модуль объекта или внешней обработки, забывая удалить директивы компиляции вроде &НаКлиенте (сообщение 16, 35). Помните: в модуле объекта и общих серверных модулях клиентские методы не поддерживаются и вызовут критические ошибки компиляции кода.

Резюме

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

← На главную