Как реализовать проверку равенства данных в реквизитах разных табличных частей документа

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

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

Разберем подробно несколько эффективных способов реализации такой проверки, проанализируем их преимущества и выясним, какой метод лучше использовать в различных ситуациях.

Способ 1. Использование метода НайтиСтроки() и перебор в цикле

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

Рассмотрим алгоритм действий:

  1. Организуем цикл по первой табличной части ТЧ_Сотрудники.
  2. Для каждого сотрудника из текущей строки формируем структуру поиска.
  3. Используем метод НайтиСтроки() для получения массива всех работ этого сотрудника из второй таблицы ТЧ_ВидыРабот.
  4. Суммируем время в найденных строках и сравниваем с эталонным значением из первой таблицы.

Проанализируем пример кода, который можно разместить в процедуре ПередЗаписью или ОбработкаПроведения модуля объекта:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    Для Каждого СтрокаСотрудник Из ТЧ_Сотрудники Цикл
        
        ПараметрыОтбора = Новый Структура;
        ПараметрыОтбора.Вставить("Сотрудник", СтрокаСотрудник.Сотрудник);
        
        НайденныеСтроки = ТЧ_ВидыРабот.НайтиСтроки(ПараметрыОтбора);
        
        СуммаДетально = 0;
        Для Каждого СтрокаРаботы Из НайденныеСтроки Цикл
            СуммаДетально = СуммаДетально + СтрокаРаботы.КоличествоЧасов;
        КонецЦикла;
        
        Если СуммаДетально <> СтрокаСотрудник.ОтработаноЧасов Тогда
            ТекстОшибки = СтрШаблон("У сотрудника %1 не совпадает время! В основной таблице: %2, в детальной: %3", 
                СтрокаСотрудник.Сотрудник, СтрокаСотрудник.ОтработаноЧасов, СуммаДетально);
            
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = ТекстОшибки;
            Сообщение.Поле = "ТЧ_Сотрудники[" + ТЧ_Сотрудники.Индекс(СтрокаСотрудник) + "].ОтработаноЧасов";
            Сообщение.УстановитьДанные(ЭтотОбъект);
            Сообщение.Сообщить();
            
            Отказ = Истина;
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

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

Способ 2. Выгрузка в Таблицу Значений и использование метода Свернуть()

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

Выясним, как это работает на практике:

  1. Выгружаем обе табличные части в объекты типа ТаблицаЗначений.
  2. Для второй таблицы (детальной) применяем метод Свернуть(), где в качестве группировки указываем сотрудника, а в качестве ресурса — количество часов.
  3. Сравниваем две таблицы.

Посмотрим на пример реализации:


ТЗ_Сводная = ТЧ_ВидыРабот.Выгрузить();
ТЗ_Сводная.Свернуть("Сотрудник", "КоличествоЧасов");

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

Этот способ эффективнее прямого перебора, так как мы подготавливаем данные один раз, а затем выполняем быстрый поиск по индексированной таблице значений.

Способ 3. Проверка через запрос (рекомендуемый метод)

Использование запроса считается наиболее профессиональным подходом в 1С. Запрос позволяет выполнить все вычисления на стороне сервера базы данных (или в памяти сервера приложений для временных таблиц), что обеспечивает максимальную производительность. Кроме того, запрос позволяет одним махом выявить всех «проблемных» сотрудников, не запуская циклы вручную.

Проанализируем логику запроса:

  1. Помещаем данные первой ТЧ во временную таблицу.
  2. Помещаем данные второй ТЧ во временную таблицу, сразу группируя их по сотруднику и суммируя часы.
  3. Выполняем Левое соединение первой таблицы со второй.
  4. В условии ГДЕ отсекаем те строки, где сумма в первой таблице не равна сумме во второй.

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Помещаем ТЧ1
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Основные ИЗ &ТЧ_Сотрудники КАК ТЧ";
Запрос.УстановитьПараметр("ТЧ_Сотрудники", ТЧ_Сотрудники.Выгрузить());
Запрос.Выполнить();

// Сравниваем
Запрос.Текст = 
"ВЫБРАТЬ
|	ВТ_Основные.Сотрудник КАК Сотрудник,
|	ВТ_Основные.ОтработаноЧасов КАК Ожидалось,
|	ЕСТЬNULL(ВТ_Детально.СуммаЧасов, 0) КАК Получено
|ИЗ
|	ВТ_Основные КАК ВТ_Основные
|		ЛЕВОЕ СОЕДИНЕНИЕ (
|			ВЫБРАТЬ
|				ТЧ.Сотрудник КАК Сотрудник,
|				СУММА(ТЧ.КоличествоЧасов) КАК СуммаЧасов
|			ИЗ
|				&ТЧ_ВидыРабот КАК ТЧ
|			СГРУППИРОВАТЬ ПО
|				ТЧ.Сотрудник) КАК ВТ_Детально
|		ПО ВТ_Основные.Сотрудник = ВТ_Детально.Сотрудник
|ГДЕ
|	ВТ_Основные.ОтработаноЧасов <> ЕСТЬNULL(ВТ_Детально.СуммаЧасов, 0)";

Запрос.УстановитьПараметр("ТЧ_ВидыРабот", ТЧ_ВидыРабот.Выгрузить());
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    // Сообщаем обо всех расхождениях
    Отказ = Истина;
КонецЦикла;

Интерактивный контроль и авторасчет (Best Practices)

Рассмотрим ситуацию с точки зрения пользователя. Блокировка проведения документа — это крайняя мера. Гораздо удобнее, если система помогает пользователю избежать ошибки еще на этапе ввода данных. Рассмотрим два способа сделать интерфейс более дружелюбным:

  1. Условное оформление: настроим в форме документа правило, которое будет окрашивать поле «Отработано часов» в красный цвет, если оно не совпадает с итогом по второй ТЧ. Это даст мгновенную визуальную обратную связь.
  2. Авторасчет: часто имеет смысл сделать поле «Отработано часов» в первой ТЧ недоступным для редактирования и вычислять его автоматически как сумму соответствующих строк из второй ТЧ. В этом случае проверка на равенство станет просто ненужной, так как данные всегда будут синхронизированы.

Для реализации авторасчета используем событие ПриИзменении реквизитов второй табличной части:


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

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

← На главную