ОПЫТ ПРАКТИЧЕСКОЙ РЕАЛИЗАЦИИ ПОДБОРОВ в 1С 8.2
Итак, предыстория. Для одной копании торгующей кабелем и электротехникой понадобилось реализовать учёт остатков и резервов кабеля в разрезе определенных свойств в конфигурации УТ (11.0.6.7 - на управляемых формах) с минимальными доработками и за минимальные деньги.
Нужно сделать отступление. Это задача уже решалась в вначале в «1С 7.7 - Торговле», а потом в «1С 8.0 - Управлении Торговлей». Тогда выяснилось, что в силу целого ряда причин, о которых мы не будем здесь говорить штатные средства системы не позволяют это реализовать (так чтобы это было удобно). Как оказалось ситуация в современной конфигурации «Управление торговлей» практически не изменилась.
Во время реализации этой задачи встал вопрос о том, как организовать подборы по остаткам и резервам кабеля из документа «Распоряжение на склад».
1) Создание команды.
В окне редактирования формы, создаём новую команду.
Размещаем её на форме в её командной панели и затем в свойствах команды:
- создаём процедуру в свойствах команды;
- настраиваем имя команды и картинку, которая будет отображаться рядом с ней.
2) Заполняем созданную процедуру исходным кодом.
&НаКлиенте
Процедура Подбор(Команда)
//Фильтр_Номенклатура = Вернуть_СписокНоменклатуры(Объект.Основание);
//АдресХранилища = ПоместитьВоВременноеХранилище(Фильтр_Номенклатура, ЭтаФорма.УникальныйИдентификатор);
АдресХранилища = "";
ОткрытьФорму("ОбщаяФорма.я_ПодборКабеля", Новый Структура("Основание,Склад,ЗакрыватьПриВыборе,АдресТов", Объект.Основание, Объект.Склад, Ложь, АдресХранилища), Элементы.Кабель);
КонецПроцедуры
-------------------------------------------------------------------------------------------------
С помощью функции ОткрытьФорму() открываем специально созданную для подборов форму, в ней с помощью структуры передаём ряд важных параметров:
ЗакрыватьПриВыборе = Ложь - этот параметр позволит организовывать множественный выбор в открываемой форме
Элементы.Кабель - наша табличная часть, куда будут передаваться подобранные данные.
Остальные параметры используются для накладывания фильтров в открываемой форме.
Комментарий.
Пожалуй правильнее было использовать модальное открытие окна для того чтобы у пользователя не было ни шанса среди многочисленных отрытых окон потерять окно подбора или закрыть окно родительского документа раньше чем окно подбора.
Затем на форме документа создаём процедуру обработки подбора в документ.
Принцип, прост. Раз мы подбираем в документе не только название товара, но и количество, значит, обрабатываем на входе структуру данных. При этом новую позицию будем добавлять в документ, а уже существующую просто добавлять по количеству к существующей.
&НаКлиенте
Процедура КабельОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
Если ТипЗнч(ВыбранноеЗначение) = Тип("Структура")
Тогда
Если ВыбранноеЗначение.Количество = 0
Тогда
Возврат;
КонецЕсли;
var_Флаг = Истина;
// -------------------
Для каждого стр из Объект.Кабель
Цикл
Если (стр.Номенклатура = ВыбранноеЗначение.Номенклатура) и
(стр.Тара = ВыбранноеЗначение.Тара)
Тогда
стр.Кол = стр.Кол + ВыбранноеЗначение.Количество;
Сообщить("Подбор ("+ВыбранноеЗначение.Количество+") "+ВыбранноеЗначение.Номенклатура + " был добавлен в строку № "+стр.НомерСтроки);
var_Флаг = Ложь;
Прервать;
КонецЕсли;
КонецЦикла;
// -------------------
Если var_Флаг
Тогда
НоваяСтрока = Объект.Кабель.Добавить();
НоваяСтрока.Номенклатура = ВыбранноеЗначение.Номенклатура;
НоваяСтрока.Тара = ВыбранноеЗначение.Тара;
НоваяСтрока.Кол = ВыбранноеЗначение.Количество;
// -------------------
Сообщить("В документ добавлено: "+НоваяСтрока.Номенклатура+" ("+НоваяСтрока.Тара+") в кол - " + НоваяСтрока.Кол + " м.");
КонецЕсли;
// -------------------
ЭтаФорма.Модифицированность = Истина;
КонецЕсли;
КонецПроцедуры
3) Создаём общую форму
Создаём именно общую форму, например для того, чтобы подбор можно было вызывать из разных документов (например, ещё из документов резервов).
Красиво размещаем элементы на форме (с помощью групп). Размещаем на форме две кнопки «Отобрать» и «Закрыть» и настраиваем запрос в свойствах объекта «Динамический список».
Текст запроса по остаткам и резервам кабеля:
ВЫБРАТЬ
Таблица_Сводная.Владелец,
Таблица_Сводная.Код,
Таблица_Сводная.Вид,
Таблица_Сводная.Номер,
Таблица_Сводная.Номер_Длины,
Таблица_Сводная.Метраж,
Таблица_Сводная.Вес,
Таблица_Сводная.Цвет,
Таблица_Сводная.Тара,
Таблица_Сводная.Поставщик,
Таблица_Сводная.Документ_Прихода,
Таблица_Сводная.Остаток,
Таблица_Сводная.Резерв,
Таблица_Сводная.Ссылка,
Таблица_Сводная.Остаток_Свободный
ИЗ
(ВЫБРАТЬ
я_Кабель.Владелец КАК Владелец,
я_Кабель.Код КАК Код,
я_Кабель.Вид КАК Вид,
я_Кабель.Номер КАК Номер,
я_Кабель.Номер_Длины КАК Номер_Длины,
я_Кабель.Метраж КАК Метраж,
я_Кабель.Вес КАК Вес,
я_Кабель.Цвет КАК Цвет,
я_Кабель.Тара КАК Тара,
я_Кабель.Поставщик КАК Поставщик,
я_Кабель.Документ_Прихода КАК Документ_Прихода,
ЕСТЬNULL(Остатки_КПП.МетражОстаток, 0) КАК Остаток,
ЕСТЬNULL(Резервы_КПП.МетражОстаток, 0) КАК Резерв,
я_Кабель.Ссылка КАК Ссылка,
ЕСТЬNULL(Остатки_КПП.МетражОстаток, 0) - ЕСТЬNULL(Резервы_КПП.МетражОстаток, 0) КАК Остаток_Свободный
ИЗ
Справочник.я_Кабель КАК я_Кабель
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.я_Кабель_Остатки.Остатки(, Склад = &Фильтр_Склад) КАК Остатки_КПП
ПО (Остатки_КПП.Тара = я_Кабель.Ссылка)
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.я_Кабель_Резервы.Остатки(, ) КАК Резервы_КПП
ПО (Резервы_КПП.Тара = я_Кабель.Ссылка)
ГДЕ
я_Кабель.ПометкаУдаления = ЛОЖЬ
И я_Кабель.Владелец В(&Фильтр_Номенклатура)) КАК Таблица_Сводная
ГДЕ
Таблица_Сводная.Остаток_Свободный > 0
Для того чтобы выделить среди других (раскрасим в желтый) главную для нас колонку свободного остатка мы настраиваем оформление колонки на форме.
4) Создаём процедуры модуля формы подбора.
Алгоритм здесь такой. При открытии формы заполняются реквизиты формы, и запускается процедура отбора по запросу. Перед отбором по запросу, текст его корректируется в зависимости от заполненных на поле фильтров. При нажатии на клавишу «Отбор» происходит всё тоже самое, но без заполнения реквизитов.
&НаСервере
Процедура Установить_Отбор()
var_ТЗ = Новый ТаблицаЗначений;
var_ТЗ = var_Основание.Товары.Выгрузить();
var_ТЗ.Свернуть("Номенклатура");
Фильтр_Номенклатура = var_ТЗ.ВыгрузитьКолонку("Номенклатура");
// -----------------------------
Тара.ТекстЗапроса =
"ВЫБРАТЬ
| Таблица_Сводная.Владелец,
| Таблица_Сводная.Код,
| Таблица_Сводная.Вид,
| Таблица_Сводная.Номер,
| Таблица_Сводная.Номер_Длины,
| Таблица_Сводная.Метраж,
| Таблица_Сводная.Вес,
| Таблица_Сводная.Цвет,
| Таблица_Сводная.Тара,
| Таблица_Сводная.Поставщик,
| Таблица_Сводная.Документ_Прихода,
| Таблица_Сводная.Остаток,
| Таблица_Сводная.Резерв,
| Таблица_Сводная.Ссылка,
| Таблица_Сводная.Остаток_Свободный
|ИЗ
| (ВЫБРАТЬ
| я_Кабель.Владелец КАК Владелец,
| я_Кабель.Код КАК Код,
| я_Кабель.Вид КАК Вид,
| я_Кабель.Номер КАК Номер,
| я_Кабель.Номер_Длины КАК Номер_Длины,
| я_Кабель.Метраж КАК Метраж,
| я_Кабель.Вес КАК Вес,
| я_Кабель.Цвет КАК Цвет,
| я_Кабель.Тара КАК Тара,
| я_Кабель.Поставщик КАК Поставщик,
| я_Кабель.Документ_Прихода КАК Документ_Прихода,
| ЕСТЬNULL(Остатки_КПП.МетражОстаток, 0) КАК Остаток,
| ЕСТЬNULL(Резервы_КПП.МетражОстаток, 0) КАК Резерв,
| я_Кабель.Ссылка КАК Ссылка,
| ЕСТЬNULL(Остатки_КПП.МетражОстаток, 0) - ЕСТЬNULL(Резервы_КПП.МетражОстаток, 0) КАК Остаток_Свободный
| ИЗ
| Справочник.я_Кабель КАК я_Кабель
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.я_Кабель_Остатки.Остатки(, ) КАК Остатки_КПП
| ПО (Остатки_КПП.Тара = я_Кабель.Ссылка)
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.я_Кабель_Резервы.Остатки(, ) КАК Резервы_КПП
| ПО (Резервы_КПП.Тара = я_Кабель.Ссылка)
| ГДЕ
| я_Кабель.ПометкаУдаления = ЛОЖЬ
| И я_Кабель.Владелец В(&Фильтр_Номенклатура)) КАК Таблица_Сводная";
//|ГДЕ
//| Таблица_Сводная.Остаток_Свободный > 0";
var_Строка_Фильтр = "";
// -----------------------------
Если ЗначениеЗаполнено(Фильтр_Вид) Тогда
var_Строка_Фильтр = var_Строка_Фильтр + "И (я_Кабель.Вид = &Фильтр_Вид) ";
КонецЕсли;
Если ЗначениеЗаполнено(Фильтр_Цвет) Тогда
var_Строка_Фильтр = var_Строка_Фильтр + "И (я_Кабель.Цвет = &Фильтр_Цвет) ";
КонецЕсли;
Если ЗначениеЗаполнено(Фильтр_Тара) Тогда
var_Строка_Фильтр = var_Строка_Фильтр + "И (я_Кабель.Тара = &Фильтр_Тара) ";
КонецЕсли;
// -----------------------------
Если ЗначениеЗаполнено(var_Строка_Фильтр) Тогда
Тара.ТекстЗапроса = СтрЗаменить(Тара.ТекстЗапроса,"В(&Фильтр_Номенклатура)", "В(&Фильтр_Номенклатура) "+var_Строка_Фильтр);
КонецЕсли;
Тара.ПроизвольныйЗапрос = Истина;
// -----------------------------
Если ЗначениеЗаполнено(Фильтр_Склад) Тогда
Тара.ТекстЗапроса = СтрЗаменить(Тара.ТекстЗапроса,".я_Кабель_Остатки.Остатки(, )", ".я_Кабель_Остатки.Остатки(, Склад = &Фильтр_Склад)");
Тара.Параметры.УстановитьЗначениеПараметра("Фильтр_Склад", Фильтр_Склад);
Иначе
Тара.ТекстЗапроса = СтрЗаменить(Тара.ТекстЗапроса,"Склад = &Фильтр_Склад", "");
КонецЕсли;
// -----------------------------
Тара.Параметры.УстановитьЗначениеПараметра("Фильтр_Номенклатура", Фильтр_Номенклатура);
Если ЗначениеЗаполнено(Фильтр_Вид) Тогда
Тара.Параметры.УстановитьЗначениеПараметра("Фильтр_Вид", Фильтр_Вид);
КонецЕсли;
Если ЗначениеЗаполнено(Фильтр_Цвет) Тогда
Тара.Параметры.УстановитьЗначениеПараметра("Фильтр_Цвет", Фильтр_Цвет);
КонецЕсли;
Если ЗначениеЗаполнено(Фильтр_Тара) Тогда
Тара.Параметры.УстановитьЗначениеПараметра("Фильтр_Тара", Фильтр_Тара);
КонецЕсли;
// -----------------------------
Элементы.Тара.Обновить();
КонецПроцедуры
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
//Сообщить(Параметры.Основание);
// ------------------------------
var_Основание = Параметры.Основание;
Фильтр_Склад = Параметры.Склад;
// ------------------------------
var_Адрес = Параметры.АдресТов;
//Фильтр_Номенклатура = Новый СписокЗначений;
//Фильтр_Номенклатура.ЗагрузитьЗначения(ПолучитьИзВременногоХранилища(var_Адрес));
//Фильтр_Номенклатура = ПолучитьИзВременногоХранилища(var_Адрес);
Установить_Отбор();
КонецПроцедуры
&НаКлиенте
Процедура Отобрать(Команда)
Установить_Отбор();
КонецПроцедуры
&НаКлиенте
Процедура ТараВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
Перем Количество;
СтандартнаяОбработка = Ложь;
Количество = Элемент.ТекущиеДанные.Остаток_Свободный;
// --------------------------------
Если ВвестиЗначение(Количество, "Введите количество поступления", Новый ОписаниеТипов("Число"))
Тогда
парам_Номен = Элемент.ТекущиеДанные.Владелец;
парам_Тара = Элемент.ТекущиеДанные.Ссылка;
Элемент.ТекущиеДанные.Остаток_Свободный = Элемент.ТекущиеДанные.Остаток_Свободный - Количество;
ЭтаФорма.ОбновитьОтображениеДанных();
// -----------------------------------
ОповеститьОВыборе( Новый Структура("Номенклатура,Тара,Количество", парам_Номен,парам_Тара,Количество ));
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПередЗакрытием(Отказ, СтандартнаяОбработка)
// УдалитьИзВременногоХранилища(var_Адрес);
КонецПроцедуры
НЕДОСТАТОК ПРИВЕДЕННОГО КОДА
Список Номенклатуры мы постоянно перевыгружаем из родительского по отношению к текущему документу, вместо того чтобы один раз заполнить в параметр.
var_ТЗ = var_Основание.Товары.Выгрузить();
Путь, который ни к чему не приведет. Сформировать в документе список значений, записать его во временное хранилище и в форме многократно его загружать из хранилища.
Первый недостаток - этого решения заключается в том, что значение из хранилища можно загрузить только один раз! Второй, в том что операция чтения из хранилища представляет собой физическую операцию чтения, в отличие от параметров формы располагающихся в оперативной памяти ПК, т.е. выполняется медленно.
Правильный путь № 1. Создать параметр формы типа «список значений» и каждый раз перед использованием преобразовывать его в массив и фильтровать по нему в запросе. При этом если в запрос подставить не массив, а «список значений», то запрос сработает только по первому параметру «списка значений».
Правильный путь № 2. Создать общий модуль с настройкой «Повторное использование возвращаемых значений» = «на время вызова», тогда возвращаемые значения экспортных функций повторно будут использоваться в разрезе входных значений их параметров.
5) Возможные варианты повышения «юзабилити»
Наглядность.
Можно создать на форме ещё один объект (например, ТаблицаЗначений) для наглядности хранения уже выбранных нами позиций.
В нем при открытии формы заполнять список уже добавленными в документ строками. Тогда при нажатии на клавишу «Выполнить» мы будем передавать в наш документ массив с выбранными нами значениями, которым мы будем перезаполнять табличную часть документа.
Множественный выбор.
Если в форму подбора открываемую функцией ОткрытьФорму() передать параметр «МножественныйВыбор» = «Истина», то в форме подбора станет возможным выбирать сразу несколько строк для добавления в документ. Для нас программистов это изменит только, то что теперь параметр выбора будет представлять собой ни строку, а массив строк который нужно обрабатывать в цикле Для каждого ... из ... Цикл.
Потеря владельца.
Для того чтобы в принципе не возникала вероятность того что документ из которого был открыт подбор закрыт, а подбор нет или среди одинаковых документов одного вида мы перестали понимать к кому документу относится наш подбор установим флаг блокировки окна владельца пока открыт подбор. При этом переключаться во все остальные окна 1С мы сможем. Это актуально для немодального открытия окна! В случае модального открытия окна все остальные окна открытой 1С нам будут недоступны до закрытия подбора.
6) НЮАНСЫ, на которые стоит обратить внимание.
Повторный подбор одного и того же товара.
Представим следующую ситуацию. Вы подобрали в документ некий товар, потом решили, что мало и решили подобрать ещё ту же номенклатуру. Логично, что не стоит размножать строки с одинаковым товаром в документе. Поэтому перед добавлением строки в таблицу выбора надо проверить, а есть ли там уже строка с выбранным товаром? Если есть, то добавим туда выбранное количество. Если нет, добавим строку.
В моём варианте это делается в процедуре обработки выбора табличной части документа.
Самый правильный вариант это делать - поиск в табличной части документа (или таблице значений на форме подбора), перебор в цикле строк табличной части (или таблицы значений тоже правильно, но не оптимально).
Например, так:
Для каждого СтрокаТоваров из ВыбранныеТовары Цикл
Поиск = Новый Структура(«Товар», Товар);
МассивНайденныхСтрок = ТЧ_Документа.НайтиСтроки(Поиск);
Если МассивНайденныхСтрок.Количество() > 0 Тогда
Строка = МассивНайденныхСтрок [0];
Иначе
Строка = ТЧ_Документа.Добавить();
Строка.Товар = Товар;
КонецЕcли;
КонецЦикла;
Переотбор по фильтрам.
Представим на секунду, что подбор мы осуществляем в окне, где в зависимости от выбранных нами значений фильтров отображается список товаров с остатками. При выборе всего остатка какого-то товара в документ, мы меняем значение фильтров и перезаполняем окно для подбора. Возможна ситуация, когда выбранный нами товар снова отобразится доступный для подбора. Не очень внимательный пользователь может повторно его выбрать и осознать свою ошибку только на этапе проведения (срабатывает контроль остатков). Но этой ситуации в принципе не возникало бы, если мы из результата запроса по остаткам вычитали бы подобранные пользователем данные, но не участвовавшие в проведении документа (например, передав таблицу значений как параметр в запрос).
Надеюсь, этот материал был Вам полезен.
С уважением, Кутанов Алексей