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