Приветствуем вас, уважаемые коллеги! Сегодня мы детально разберем одну из важнейших тем в разработке на платформе 1С:Предприятие 7.7 — эффективную работу с универсальным объектом ТаблицаЗначений. Несмотря на почтенный возраст этой технологической платформы, тысячи предприятий продолжают успешно эксплуатировать решения на ее базе, а перед программистами регулярно встают нетривиальные задачи поддержки и доработки легаси-кода.
В этой статье мы подробно проанализируем механизм расчета сумм с использованием метода Итог(), выясним, почему обращение к колонкам по их порядковым номерам представляет огромную опасность для стабильности системы, и выработаем архитектурно верное решение для сложной методологической задачи — раздельного учета и вывода в отчеты сумм НДС по ставкам 18% и 20% в переходный период.
Начнем с основ. Объект ТаблицаЗначений (сокращенно ТЗ) в 1С 7.7 является двумерным динамическим массивом данных, который активно применяется для промежуточного хранения информации, группировки показателей и подготовки данных к выводу в печатные формы. Для быстрого вычисления сумм по числовым столбцам у этого объекта предусмотрен встроенный метод Итог().
Синтаксис метода предельно прост:
Итог(<Колонка>)
Параметр Колонка может принимать значения двух типов:
"СуммаНДС").9 или 13).Рассмотрим простейший практический пример инициализации таблицы, ее заполнения и расчета итогов:
// Создание объекта и добавление типизированных колонок
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Товар", "Справочник.Номенклатура");
ТЗ.НоваяКолонка("Количество", "Число", 10, 3);
ТЗ.НоваяКолонка("Сумма", "Число", 15, 2);
// Наполнение таблицы тестовыми данными
ТЗ.НоваяСтрока();
ТЗ.Товар = КонкретныйТовар1;
ТЗ.Количество = 5;
ТЗ.Сумма = 1500.50;
ТЗ.НоваяСтрока();
ТЗ.Товар = КонкретныйТовар2;
ТЗ.Количество = 10;
ТЗ.Сумма = 3000.00;
// Расчет итогов по колонкам
ВсегоКоличество = ТЗ.Итог("Количество");
ВсегоСумма = ТЗ.Итог("Сумма");
Давайте выясним причину, по которой использование числовых индексов (например, конструкция ТЗ.Итог(9)) считается крайне плохой практикой разработки, или так называемым «хрупким кодом».
Представим реальную ситуацию: вы написали сложный аналитический отчет, где расчет итоговых показателей жестко привязан к номерам колонок. Программа запущена и успешно работает. Спустя несколько месяцев бизнес-требования меняются, и другой программист (или вы сами) добавляет новую информационную колонку (например, «Артикул» или «Склад») в самое начало или середину таблицы значений. При этом все последующие колонки автоматически сдвигаются вправо на один шаг.
Что произойдет в этом случае?
ТЗ.Итог(9) продолжит выполняться, но теперь он будет рассчитывать сумму по совершенно другой колонке, которая сдвинулась на девятую позицию.Именно поэтому для обеспечения отказоустойчивости и долговечности вашего программного кода всегда обращайтесь к колонкам по их строковым именам: ТЗ.Итог("Сумма"). Исключением из этого правила могут служить только универсальные алгоритмы обхода колонок в циклах, когда требуется собрать итоги по абсолютно всем имеющимся числовым полям динамической таблицы:
// Безопасный универсальный сбор итогов по всем колонкам в цикле
ОбщийИтог = 0;
Для НомерКолонки = 1 По ТЗ.КоличествоКолонок() Цикл
// Проверяем тип колонки, чтобы избежать ошибок суммирования нечисловых данных
Если ТЗ.ПолучитьПараметрыКолонки(НомерКолонки) = "Число" Тогда
ОбщийИтог = ОбщийИтог + ТЗ.Итог(НомерКолонки);
КонецЕсли;
КонецЦикла;
Рассмотрим практическую запару, с которой столкнулся автор темы на форуме. При переходе со ставки НДС 18% на 20% (произошедшем 1 января 2019 года) возникла необходимость формировать отчеты за периоды, захватывающие оба этих интервала времени (например, за четвертый квартал 2018 и первый квартал 2019 года).
Автор попытался написать в ячейке итоговой строки макета следующее условное выражение:
?(Дата1 <= '31.12.2018', ТЗ.Итог(13), ТЗ.Итог(18))
Где Дата1 — дата начала периода отчета. Давайте проанализируем, почему такой подход в корне неверен и приводит к некорректным результатам:
Дата1 определяет начало всего периода отчета. Если отчет формируется за весь первый квартал 2019 года, дата начала будет равна 01.01.2019. Условие отработает по ветке «Иначе», и система выведет итог только по ставке 20% (колонка 18), полностью проигнорировав возможные документы конца 2018 года, если они попали в выборку.Давайте разберем по шагам, как решить эту задачу правильно, надежно и красиво. Для этого мы применим классический паттерн подготовки данных: перенесем всю сложную логику распределения из шаблона печатной формы непосредственно в программный код модуля отчета.
Вместо того чтобы пытаться производить условные расчеты в момент вывода итоговой строки макета, мы создадим в нашей ТаблицеЗначений две независимые числовые колонки — НДС18 и НДС20. При обходе документов мы будем анализировать дату каждой операции и записывать сумму налога строго в соответствующую колонку.
Рассмотрим пример реализации этого алгоритма в коде:
// Инициализация Таблицы Значений с жесткой типизацией колонок
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("ДокументСФ", "Документ.СчетФактура");
ТЗ.НоваяКолонка("ДатаДок", "Дата");
ТЗ.НоваяКолонка("СуммаНДС18", "Число", 15, 2);
ТЗ.НоваяКолонка("СуммаНДС20", "Число", 15, 2);
// Определяем граничную дату перехода на новую ставку НДС
ГраничнаяДата = Дата(2018, 12, 31);
// Выборка документов за период отчета
ВыборкаСФ = СоздатьОбъект("Документ.СчетФактура");
ВыборкаСФ.ВыбратьДокументы(ДатаНачала, ДатаКонца);
Пока ВыборкаСФ.ПолучитьДокумент() = 1 Цикл
// Пропускаем непроведенные документы
Если ВыборкаСФ.Проведен() = 0 Тогда
Продолжить;
КонецЕсли;
ТЗ.НоваяСтрока();
ТЗ.ДокументСФ = ВыборкаСФ.ТекущийДокумент();
ТЗ.ДатаДок = ВыборкаСФ.ДатаДок;
// Распределяем суммы НДС в зависимости от даты документа
Если ВыборкаСФ.ДатаДок <= ГраничнаяДата Тогда
ТЗ.СуммаНДС18 = ВыборкаСФ.СуммаНДС;
ТЗ.СуммаНДС20 = 0;
Иначе
ТЗ.СуммаНДС18 = 0;
ТЗ.СуммаНДС20 = ВыборкаСФ.СуммаНДС;
КонецЕсли;
КонецЦикла;
Благодаря такому подходу в нашей таблице значений всегда будут содержаться абсолютно достоверные, разделенные по ставкам суммы. В строке итогов отчета нам останется лишь вывести простые выражения:
ТЗ.Итог("СуммаНДС18")ТЗ.Итог("СуммаНДС20")При реализации данного алгоритма на практике начинающие разработчики часто сталкиваются с неочевидными системными сбоями. Давайте подробно проанализируем эти нюансы, чтобы застраховать наш код от ошибок.
В отличие от платформы 1С v8, где типы данных в коллекциях более гибкие, в 1С 7.7 метод ТЗ.Итог() вернет 0, если колонка не была предварительно жестко типизирована при создании. Если вы напишете просто ТЗ.НоваяКолонка("НДС18"), платформа создаст колонку неопределенного типа. При записи чисел туда они могут быть неявно преобразованы в строки, и суммирование не сработает. Всегда явно указывайте тип «Число» и его разрядность!
В коде автора темы присутствует конструкция ТЗ.ДатаСФ <= '31.12.2018'. Использование строковых констант в одинарных кавычках для сравнения дат — источник повышенной опасности. Интерпретация такой строки зависит от региональных настроек Windows на компьютере пользователя или сервере приложений. Если формат даты в системе настроен как MM/DD/YYYY, сравнение отработает неверно.
Рекомендуется использовать безопасные варианты записи:
Дата(2018, 12, 31).'~31.12.2018~'.Если ваша таблица значений содержит десятки тысяч строк, размещение прямых вызовов методов ТЗ.Итог() в формулах ячеек макета печатной формы (mxl) приведет к катастрофическому падению производительности. Дело в том, что движок построения отчетов 1С 7.7 будет вычислять итог по всей таблице заново при выводе каждой секции, где упомянута эта формула.
Для ускорения работы отчета в разы примените следующий прием:
Рассмотрим пример оптимизированного вывода секции подвала отчета:
// Предварительный расчет итогов в переменные модуля
ИтогНДС18 = ТЗ.Итог("СуммаНДС18");
ИтогНДС20 = ТЗ.Итог("СуммаНДС20");
ОбщийИтог = ИтогНДС18 + ИтогНДС20;
// Печать подвала отчета (в макете в ячейках прописаны переменные ИтогНДС18, ИтогНДС20, ОбщийИтог)
Таб.ВывестиСекцию("Подвал");
Если вы вывели ТаблицуЗначений на экранную форму (диалог) для интерактивного ввода или просмотра данных пользователем, вы должны помнить, что итоги по колонкам не пересчитываются автоматически при изменении чисел в строках. Чтобы реализовать «живой» пересчет сумм на форме, используйте формулы колонок ТЗ на форме, вызывающие принудительное обновление интерфейса с помощью метода Форма.Обновить(0) или напрямую пересчитывающие текстовые реквизиты итогов на форме диалога.
Мы подробно разобрали особенности работы с итогами таблиц значений в 1С 7.7. Написание стабильного кода требует от разработчика понимания внутренних механизмов платформы. Избегайте использования индексов колонок, всегда строго типизируйте данные, аккуратно работайте с датами и оптимизируйте вывод отчетов. Эти простые правила позволят вашим решениям работать быстро и безотказно в любых условиях!