Как рассчитать значение в колонке табличной части по предыдущей строке в 1С 8.3

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

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

В этой статье мы подробно разберем, как получить данные из предыдущей строки табличной части в управляемом приложении 1С 8.3, проанализируем возможные подводные камни (такие как интерактивная сортировка или редактирование строк «задним числом») и реализуем отказоустойчивый алгоритм расчета.

Вариант №1. Получение предыдущей строки по физическому индексу коллекции

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

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


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

В чем опасность этого подхода? Физический индекс строки в коллекции объекта Объект.ТЧПоказания жестко привязан к порядку добавления строк. Если пользователь кликнет по заголовку любой колонки на форме, платформа выполнит визуальную сортировку строк. При этом визуальный порядок на экране изменится, а физические индексы строк в коллекции останутся прежними! В результате выражение ТекИндекс - 1 вернет совершенно другую строку, которая визуально может находиться в конце или середине таблицы, что приведет к грубейшей ошибке в расчетах.

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

  1. РазвешитьСортировку — снимаем флаг (устанавливаем в значение Ложь).
  2. РазрешитьИзменениеПорядкаСтрок — снимаем флаг (устанавливаем в значение Ложь).

Вариант №2. Использование локальной переменной формы (быстрый ввод)

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


&НаКлиенте
Перем ПредыдущееПоказаниеВесов;

&НаКлиенте
Процедура ТЧПоказанияПоказаниеВесовПриИзменении(Элемент)
    
    СтрокаТЧ = Элементы.ТЧПоказания.ТекущиеДанные;
    Если СтрокаТЧ = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    Если ПредыдущееПоказаниеВесов = Неопределено Тогда
        // Инициализируем начальное значение для первой сессии ввода
        ПредыдущееПоказаниеВесов = 0;
    КонецЕсли;
    
    СтрокаТЧ.Вес = СтрокаТЧ.ПоказаниеВесов - ПредыдущееПоказаниеВесов;
    
    // Перезаписываем переменную для следующей строки
    ПредыдущееПоказаниеВесов = СтрокаТЧ.ПоказаниеВесов;
    
КонецПроцедуры

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

Вариант №3. Каскадный пересчет таблицы (Отраслевой стандарт)

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

Поскольку коллекция табличной части на клиенте обычно невелика (в пределах нескольких десятков или сотен строк), полный обход таблицы в цикле на клиенте выполняется мгновенно и абсолютно незаметно для пользователя. Напишем универсальную процедуру пересчета, которую будем вызывать при любом изменении ключевых полей.


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

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

Разберем преимущества каскадного метода:

  1. Устойчивость к редактированию: Если оператор изменит показание весов в строке №3, система автоматически пересчитает строку №3, а также строку №4, №5 и все последующие, базируясь на обновленных данных строки №3.
  2. Простота отладки: Код лишен сложных условий получения индексов и не зависит от системных идентификаторов строк 1С.
  3. Универсальность: Эту же процедуру пересчета можно вызвать по кнопке «Рассчитать веса» на панели инструментов табличной части, если автоматический интерактивный расчет по каким-то причинам не требуется.

Вариант №4. Использование идентификаторов строк в Управляемых Формах

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

Давайте посмотрим на пример точечного получения предыдущей строки по её идентификатору:


&НаКлиенте
Процедура РассчитатьРазницуДляТекущейСтроки()
    
    ТекИдентификатор = Элементы.ТЧПоказания.ТекущаяСтрока;
    
    Если ТекИдентификатор <> Неопределено И ТекИдентификатор > 0 Тогда
        
        ТекущиеДанные = Объект.ТЧПоказания.НайтиПоИдентификатору(ТекИдентификатор);
        
        // Пытаемся получить строку, созданную непосредственно перед текущей
        ПредыдущиеДанные = Объект.ТЧПоказания.НайтиПоИдентификатору(ТекИдентификатор - 1);
        
        Если ПредыдущиеДанные <> Неопределено Тогда
            ТекущиеДанные.Вес = ТекущиеДанные.ПоказаниеВесов - ПредыдущиеДанные.ПоказаниеВесов;
        Иначе
            // Предыдущая строка не найдена (например, текущая строка первая в списке)
            ТекущиеДанные.Вес = ТекущиеДанные.ПоказаниеВесов;
        EndIf;
        
    КонецЕсли;
    
КонецПроцедуры

Этот способ более технологичен, но все еще имеет ограничения при ручной сортировке строк пользователем, поэтому для учетных задач взвешивания и нарастающих итогов мы настоятельно рекомендуем использовать Вариант №3 (Каскадный пересчет).

Автоматизация ввода и интеграция с весовым оборудованием

При проектировании подобных систем взвешивания важно заложить архитектуру, которая предусматривает как автоматический режим работы, так и ручной ввод (так называемый «План Б» на случай выхода оборудования из строя):

← На главную