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