Как сравнить табличные части документа и документа-основания в 1С?

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

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

Разберем основные архитектурные нюансы и программные способы реализации такой проверки.

Анализ задачи и ключевые особенности сопоставления данных

Перед тем как писать алгоритм сверки, проанализируем важные нюансы прикладной логики:

  1. Группировка дублей: В табличной части одна и та же номенклатура может быть внесена несколькими строками (например, с разными ценами или комментариями). Прямое сравнение строк «один к одному» по индексу приведет к ошибкам. Перед сравнением таблицы необходимо сворачивать по ключевым аналитикам с суммированием количества.
  2. Разрезы учета: Если в конфигурации включен учет по характеристикам, сериям или качеству, сопоставление исключительно по полю номенклатуры даст ложные срабатывания. Соединение и поиск должны выполняться по всему составному ключу аналитики (номенклатура + характеристика + серия).
  3. Коэффициенты упаковок: Если в текущем документе и документе-основании используются разные единицы измерения одной позиции, перед сопоставлением нужно привести количество к базовой единице через умножение на Коэффициент.
  4. Чтение основания: Если документ-основание уже записан в базе данных, нет необходимости выгружать его табличную часть в память и передавать параметром. Оптимально прочитать его данные напрямую из базы в запросе.

Способ 1. Проверка через запрос с левым соединением (Оптимальный вариант)

Рассмотрим наиболее производительный и универсальный способ контроля. Мы передаем в запрос несохраненную табличную часть текущего документа, сворачиваем ее, а данные основания выбираем напрямую из таблицы базы данных. С помощью ЛЕВОГО СОЕДИНЕНИЯ мы сразу выявляем позиции, которых вовсе нет в основании (значение количества из основания будет NULL), а также позиции, где количество в текущем документе превышает количество в основании.

Посмотрим на пример реализации функции проверки в модуле объекта (например, в обработчике ОбработкаПроверкиЗаполнения):


Процедура ПроверитьСоответствиеОснованию(Отказ)
	
	Если Не ЗначениеЗаполнено(ДокументОснование) Тогда
		Возврат;
	КонецЕсли;
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	ТоварыДокумента.Номенклатура КАК Номенклатура,
	|	ТоварыДокумента.Характеристика КАК Характеристика,
	|	СУММА(ТоварыДокумента.Количество) КАК Количество
	|ПОМЕСТИТЬ ВТ_ТоварыТекущие
	|ИЗ
	|	&ТаблицаТоваров КАК ТоварыДокумента
	|СГРУППИРОВАТЬ ПО
	|	ТоварыДокумента.Номенклатура,
	|	ТоварыДокумента.Характеристика
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ТоварыОснования.Номенклатура КАК Номенклатура,
	|	ТоварыОснования.Характеристика КАК Характеристика,
	|	СУММА(ТоварыОснования.Количество) КАК Количество
	|ПОМЕСТИТЬ ВТ_ТоварыОснования
	|ИЗ
	|	Документ.ЗаказКлиента.Товары КАК ТоварыОснования
	|ГДЕ
	|	ТоварыОснования.Ссылка = &ДокументОснование
	|СГРУППИРОВАТЬ ПО
	|	ТоварыОснования.Номенклатура,
	|	ТоварыОснования.Характеристика
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	ВТ_ТоварыТекущие.Номенклатура КАК Номенклатура,
	|	ВТ_ТоварыТекущие.Характеристика КАК Характеристика,
	|	ВТ_ТоварыТекущие.Количество КАК КоличествоТекущее,
	|	ЕСТЬNULL(ВТ_ТоварыОснования.Количество, 0) КАК КоличествоОснование,
	|	ВЫБОР
	|		КОГДА ВТ_ТоварыОснования.Номенклатура ЕСТЬ NULL ТОГДА ИСТИНА
	|		ИНАЧЕ ЛОЖЬ
	|	КОНЕЦ КАК ЭтоЛишняяСтрока
	|ИЗ
	|	ВТ_ТоварыТекущие КАК ВТ_ТоварыТекущие
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ТоварыОснования КАК ВТ_ТоварыОснования
	|		ПО ВТ_ТоварыТекущие.Номенклатура = ВТ_ТоварыОснования.Номенклатура
	|			И ВТ_ТоварыТекущие.Характеристика = ВТ_ТоварыОснования.Характеристика
	|ГДЕ
	|	ВТ_ТоварыОснования.Номенклатура ЕСТЬ NULL
	|	ИЛИ ВТ_ТоварыТекущие.Количество > ВТ_ТоварыОснования.Количество";
	
	Запрос.УстановитьПараметр("ТаблицаТоваров", ЭтотОбъект.Товары.Выгрузить());
	Запрос.УстановитьПараметр("ДокументОснование", ЭтотОбъект.ДокументОснование);
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	Пока Выборка.Следующий() Цикл
		Отказ = Истина;
		Если Выборка.ЭтоЛишняяСтрока Тогда
			ТекстСообщения = СтрШаблон(
				"Позиция ""%1"" отсутствует в документе-основании!", 
				Выборка.Номенклатура
			);
		Иначе
			ТекстСообщения = СтрШаблон(
				"По позиции ""%1"" превышено количество! В документе: %2, в основании: %3",
				Выборка.Номенклатура,
				Выборка.КоличествоТекущее,
				Выборка.КоличествоОснование
			);
		КонецЕсли;
		
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = ТекстСообщения;
		Сообщение.Поле = "Товары";
		Сообщение.УстановитьДанные(ЭтотОбъект);
		Сообщение.Сообщить();
	КонецЦикла;
	
КонецПроцедуры

Способ 2. Двусторонний анализ через полное соединение

Если перед нами стоит задача не просто ограничить проведение текущего документа, но и показать пользователю полную ведомость расхождений (что добавлено лишнее, что не довыбрано из основания, где не сошлись количества), применим ПОЛНОЕ СОЕДИНЕНИЕ.

Разберем структуру такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ЕСТЬNULL(ВТ_Текущие.Номенклатура, ВТ_Основание.Номенклатура) КАК Номенклатура,
|	ЕСТЬNULL(ВТ_Текущие.Количество, 0) КАК КоличествоТекущее,
|	ЕСТЬNULL(ВТ_Основание.Количество, 0) КАК КоличествоОснование,
|	ЕСТЬNULL(ВТ_Текущие.Количество, 0) - ЕСТЬNULL(ВТ_Основание.Количество, 0) КАК Разница
|ИЗ
|	ВТ_ТоварыТекущие КАК ВТ_Текущие
|		ПОЛНОЕ СОЕДИНЕНИЕ ВТ_ТоварыОснования КАК ВТ_Основание
|		ПО ВТ_Текущие.Номенклатура = ВТ_Основание.Номенклатура
|ГДЕ
|	ЕСТЬNULL(ВТ_Текущие.Количество, 0) - ЕСТЬNULL(ВТ_Основание.Количество, 0) <> 0";

Проанализируем результаты группировки:

  1. Если КоличествоОснование = 0 — строка добавлена в документ сверх основания.
  2. Если КоличествоТекущее = 0 — позиция из основания полностью отсутствует в подчиненном документе.
  3. Если Разница > 0 — по позиции указано большее количество, чем заложено в основании.

Способ 3. Программная сверка коллекций без создания запроса

Для небольших табличных частей в простых конфигурациях или внешних обработках можно выполнить проверку средствами встроенного языка с использованием объекта ТаблицаЗначений.

Рассмотрим пример алгоритма с предварительным сворачиванием и индексацией:


// 1. Копируем и сворачиваем текущую табличную часть
ТаблицаТекущая = ЭтотОбъект.Товары.Выгрузить();
ТаблицаТекущая.Свернуть("Номенклатура", "Количество");

// 2. Копируем и сворачиваем табличную часть документа-основания
ТаблицаОснования = ЭтотОбъект.ДокументОснование.Товары.Выгрузить();
ТаблицаОснования.Свернуть("Номенклатура", "Количество");

// 3. Индексируем таблицу основания для ускорения поиска
ТаблицаОснования.Индексы.Добавить("Номенклатура");

СтруктураПоиска = Новый Структура("Номенклатура");

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

Адресный вывод сообщений об ошибках

Для создания удобного интерфейса рекомендуется выводить ошибки с привязкой к конкретной строке табличной части. Когда мы используем объект СообщениеПользователю, заполняем свойства Поле (в формате "Товары[Индекс].Количество") и вызываем метод УстановитьДанные(ЭтотОбъект), платформа 1С автоматически активизирует ошибочную ячейку и подсвечивает ее красной рамкой непосредственно на форме документа.

← На главную