Автор Тема: Основы работы с V7 через OLE.  (Прочитано 9647 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Granata005

  • злой бомб
  • Статский советник
  • *****
  • Сообщений: 891
  • Репутация: 31
  • Пол: Мужской
  • Lantorg.com
Основы работы с V7 через OLE.
« : ЅЮпСам 15, 2007, 02:45:47 pm »
(с) Андрей Егоров (не я)
________________________________________

Основные преимущества, благодаря которым OLE активно используется в работе с V7:
•   Для вызывающей базы по барабану – какой тип имеет вызываемая база (DBF или SQL)
•   Объектами вызываемой базы можно управлять практически всеми известными методами работы с объектами V7 (т.е. со справочниками работают методы справочников, с документами – методы документов, и т.д.). Соответственно, можно напрямую решить – стоит отрабатывать конкретные объекты вызываемой базы, или же пропустить их.
1. Присоединение к базе V7 через OLE.
БазаОле=СоздатьОбъект("V77.Application");
// Получаем доступ к OLE объекту V7


Очень важно знать, какая версия V7 установлена на компьютере (локальная, сетевая или SQL), так как каждая из них прописывает в реестре свое значение ключа для запуска через OLE:
•   Локальная версия (на одного пользователя): V77L.Application
•   Сетевая версия: V77.Application Версия SQL: V77S.Application
Далее, вместо термина «вызываемая база» будет написано просто «база OLE», а вместо термина «вызывающая база» – «местная база«.
Теперь, мы должны знать несколько параметров для запуска базы OLE: каталог базы, имя пользователя и пароль. Ну, наверное, ещё понадобится возможность запустить базу OLE в монопольном режиме ;-).

Запускаем базу следующим образом:

КаталогБазыОЛе = "C:\program files\1cv77\МояБаза\";
ПользовательОле = "Администратор";
ПарольОле = "qwerty";

МонопольныйРежимOLE = " /m";
// для немонопольного запуска указать пустую строку!

ЗапускБезЗаставки = 1;
// для появления заставки поставьте "0"

РезультатПодключения =
БазаОле.Initialize ( БазаОле.RMTrade , "/d" +
Сокрлп(КаталогБазыОле) + " /n" +
Сокрлп(ПользовательОле)+ " /p" +
Сокрлп(ПарольОле) + МонопольныйРежимOLE ,
?(ЗапускБезЗаставки = 1,"NO_SPLASH_SHOW",""));

Если РезультатПодключения = 0 Тогда
Предупреждение("Ошибка подключения.");
КонецЕсли;

Комментарий: функции СокрЛП() указаны в примере на тот случай, если пользователь захочет указанные выше переменные сделать реквизитами диалога (проблема при этом состоит в том, что в алгоритм программа передаст полное значение реквизита, т.е. допишет в конце значения то количество пробелов, которое необходимо для получения полной длины строки, указанной в свойствах реквизита диалога).

2. Доступ к объектам базы OLE

Запомните на будущее как непреложный факт:
1.   Из местной базы в базу OLE (и, соответственно, наоборот) напрямую методом присвоения можно перенести только числовые значения, даты и строки ограниченной длины. Т.е. местная база «поймет» прекрасно без дополнительных алгоритмов преобразования полученного значения только простые типы значений. Кроме того, под ограничением строк подразумевается проблемы с пониманием в местной базе реквизитов объектов базы OLE типа Строка неограниченной длины. К этому же еще надо добавить и периодические реквизиты. Естественно, под методом присвоения подразумеваются и попытки сравнения объектов из разных баз.
2.   Есть проблемы при попытке перенести пустую дату – OLE может ее конвертировать, например, в 31.12.1899 и т.п. Поэтому вам лучше заранее выяснить те значения, которые могут появится в местной базе при переносе пустых дат, чтобы предусмотреть условия преобразования их в местной базе.
Доступ к константам базы OLE
ЗначениеКонстантыOLE = БазаОле.Константа.ДатаЗапретаРедактирования;

Доступ к справочникам и документам базы OLE (через функцию CreateObject)
СпрOLE = БазаОле.CreateObject("Справочник.Фирмы");
ДокOLE = БазаОле.CreateObject("Документ.РасходнаяНакладная");
// "СоздатьОбъект" в OLE не работает!


После создания объекта справочника или документа к нему применимы все методы касающиеся соответствующего объекта V7:

СпрОле.ВыбратьЭлементы();
Пока СпрОле.ПолучитьЭлемент() Цикл
Сообщить(Спр.Наименование);
КонецЦикла;


Заметьте, что если В операторе Сообщить вместо Спр.наименование вы укажете Спр.ТекущийЭлемент(), то вместо строкового или числового представления этого элемента программа выдаст просто "OLE". Именно это я и имел в виду, когда говорил, что напрямую мало что можно вызвать.
Гарантированно не будут работать методы ТекущийЭлемент() и ТекущийДокумент() (ошибки V7 не будет, но и результат работы будет нулевой).
 Рассмотрим следующий пример:
СпрOLE = БазаОле.CreateObject("Справочник.Фирмы");
// это справочник в базе OLE
Док = СоздатьОбъект("Документ.РасходнаяНакладная");
// а это документ в местной базе
Док.Новый(); // создаем новый документ в местной базе
СпрOLE.НайтиПоКоду(1,0);
// ищем в базе OLE фирму с кодом "1"
Док.Фирма = СпрOLE.ТекущийЭлемент();
// такой метод не сработает, т.к. справа от "=" стоит
// объект не местной базы, и местная база его не поймёт
// однако сработает следующий метод:
Спр = СоздатьОбъект("Справочник.Фирмы");
// создаем объект справочника местной базы
Спр.найтиПоКоду(СпрОле.Код,0);
// Или
Спр.НайтиПоНаименованию(СпрОле.Наименование,0,0);
// т.е. СпрОле.Код и Спр.Наименование являются обычными
// числовыми/строковыми значениями,
// которые понимает местная база

Док.Фирма = Спр.ТекущийЭлемент();
// Вот теперь все в порядке, т.к. с обоих сторон метода
// стоят объекты только местной базы
Отсюда вывод: возможность доступа к объектам базы V7 через OLE требуется, в основном, только для определенной задачи – получить доступ к реквизитам определенного элемента справочника или документа.
Однако не забываем, что объекты базы OLE поддерживают все методы работы с ними, в том числе и Новый(). Приведем пример, противоположный предыдущему:
ДокОле = CreateObject("Документ.РасходнаяНакладная");
// Создаем документ в базе OLE

ДокОле.Новый();

Спр = СоздатьОбъект("Справочник.Фирмы");
// В местной базе получаем доступ к справочнику

Спр.НайтиПоКоду(1,0);
// Находим в местной базе фирму с кодом 1

ДокОле.Фирма = Спр.ТекущийЭлемент();
// такой метод не сработает

// однако сработает следующий метод:

СпрОле = СоздатьОбъект("Справочник.Фирмы");
// создаем объект справочника базы OLE

СпрОле.найтиПоКоду(Спр.Код,0);
// Или
СпрОле.НайтиПоНаименованию(Спр.Наименование,0,0);
// т.е. Спр.Код и Спр.наименование являются
// обычными числовыми/строковыми значениями,
// которые понимает база OLE

ДокОле.Фирма = СпрОле.ТекущийЭлемент();
// Вот теперь все в порядке, т.к. с обоих сторон
// метода стоят объекты базы OLE

ДокОле.Записать();
// запишем документ в базе OLE

Если ДокОле.Провести()=0 Тогда
Сообщить("Ничего не вышло");
КонецЕсли;
Доступ к регистрам базы OLE: не сложнее справочников и документов
РегОле=БазаOLE.CreateObject("Регистр.ОстаткиТоваров");
РегОле.ВыбратьИтоги();

Пока РегОле.ПолучитьИтог()=1 Цикл
// Не забываем, что надо указывать наименование
Сообщить(
"Остаток для " +
Рег.Товар.Наименование +
" на складе " +
Рег.Склад.Наименование +
" равен " + Рег.ОстатокТовара
);
КонецЦикла;
Доступ к перечислениям базы OLE (аналогичен константе)
ЗначениеПеречисленияOLE = БазаОле.Перечисление.Булево.НеЗнаю; // ;-)
Заметьте, что пользы для местной базы от переменной ЗначениеПеречисленияOLE особо-то и нет, ведь, подобно справочнику и документу, перечисление также напрямую недоступно для местной базы.
Пожалуй, пример работы с ними может быть следующим (в качестве параметра условия):

СмотретьТолькоВозвратыПоставщикам = 1;
// предположим, что это флажок в форме диалога,
// который мы либо устанавливаем, либо снимаем

ДокОле = CreateObject("Документ.РасходнаяНакладная");
ДокОле.ВыбратьДокументы(НачДата,КонДата);
// НачДата и КонДата также реквизиты формы
// диалога, но база OLE прекрасно их понимает -
// ведь это же даты

Пока ДокОле.ПолучитьДокумент()=1 Цикл
Если СмотретьТолькоВозвратыПоставщикам = 1 Тогда

Если ДокОле.ПризнакНакладной <>
БазаОле.Перечисление.ПризнРасхНакл.ВозвратПоставщику
Тогда
Продолжить;
КонецЕсли;

Иначе

Если ДокОле.ПризнакНакладной =
БазаОле.Перечисление.ПризнРасхНакл.ВозвратПоставщику
Тогда
Продолжить;
КонецЕсли;

КонецЕсли;

Сообщить(
ДокОле.Вид() + " № "+ДокОле.НомерДок +
" от " + ДокОле.датаДок
);

КонецЦикла;
Доступ к счетам базы OLE

СчтОле=БазаОле.CreateObject("Счет");
СчтОле.НайтиПоКоду("10.5");
// нашли в базе OLE счет 10.5


Доступ к ВидамСубконто базы OLE (аналогичен перечислению)
ВидСубконтоКонтрагентыОле = БазаОле.ВидыСубконто.Контрагенты;

По аналогии со справочниками и документами работает объект Периодический, план счетов работает по аналогии с ВидомСубконто, ну и далее в том же духе.
Отдельную главу посвятим запросу, а сейчас – стоп. Самое-то главное забыли!

Доступ к функциям и процедурам глобального модуля базы OLE
Как же я про это забыл-то, а? Поскольку при запуске базы автоматически компилируется глобальный модуль, то нам становятся доступны его функции и процедуры (поправлюсь – только те, у которых выставлена опция Экспорт). Плюс к ним еще и различные системные функции V7. А доступны они нам через функцию V7 EvalExpr(). Приведем примеры работы с базой OLE:
ДатаАктуальностиОле = БазаОле.EvalExpr("ПолучитьДатуТА()");
// Возвращает дату актуальности

ИмяПользователяОле = БазаОле.EvalExpr("ИмяПользователя()");
// возвращает строку

// попробуем теперь получить числовое значение НДС у
// элемента номенклатуры через функцию глобального
// модуля ПроцентНДС(СтавкаНДС)

ТовОле = БазаОле.CreateObject("Справочник.Номенклатура");
ТовОле.ВыбратьЭлементы();
// Найдем элемент справочника (не группа!)
Пока ТовОле.ПолучитьЭлемент()=1 Цикл
Если ТовОле.ЭтоГруппа()=0 Тогда
Прервать;
КонецЕсли;
КонецЦикла;

ЧисловоеЗначениеПроцентаНДСТовараОле =
БазаОле.EvalExpr("ПроцентНДС(Перечисление.ЗначенияНДС." +
ТовОле.СтавкаНДС.Идентификатор()+")");
На самом деле, в последней строке примера я исхитрился и забежал немного вперед. Дело в том, что как и запрос (см. отдельную главу), так и EvalExpr() выполняются внутри базы OLE, причем команды передаются им строкой, и поэтому приходится ломать голову, как передать им необходимые значения объектов базы OLE в строке, сформированной в местной базе.

3. Алгоритмы преобразования объектов в удобоваримый вид между базами
Ясно, что алгоритмы преобразования нужны не только для переноса объектов в между и базами, но и для такой простой задачи, как попытки сравнить их между собой. И еще раз обращу внимание: ОБЪЕКТЫ ОДНОЙ БАЗЫ ПРЕКРАСНО ПОНИМАЮТ ДРУГ ДРУГА, ПРОБЛЕМЫ ВОЗНИКАЮТ ТОЛЬКО КОГДА ВЫ НАЧИНАЕТЕ СВЯЗЫВАТЬ МЕЖДУ СОБОЙ ОБЪЕКТЫ РАЗНЫХ БАЗ.
Команда
ДокОле.Фирма=СпрОле.ТекущийЭлемент();
// где ДокОле – документ базы OLE,
// a СпрОле – справочник "Фирмы" базы OLE

будет прекрасно работать без ошибок. Не забывайте это, чтобы не перемудрить с алгоритмами!
Итак, повторяюсь, что и перенести напрямую, и просто сравнить можно только даты (причем не пустые), числа и строки ограниченной длины. Вопрос – как же нам сравнить агрегатные объекты из разных баз (не числа, не даты и не строки), т.е. как их преобразовать в эту самую строку/число/дату?.

Преобразование справочников и документов базы OLE (если есть аналоги в местной базе).
В принципе, преобразование их было уже рассмотрено в примерах выше и сводится к поиску их аналогов в местной базе. Могу еще раз привести пример, заодно с использованием регистров:
// ВыбФирма, НачДата, КонДата, ВыбДокумент
// это реквизиты диалога в местной базе
// причем они все указаны, т.е. не пустые (чтобы не делать
//лишних команд в примере)

ДокОле = БазаОле.CreateObject("Документ.РасходнаяНакладная");
// объект базы OLE

Док = СоздатьОбъект("Документ.РасходнаяНакладная");
// а это его аналог в местной базе

Спр = СоздатьОбъект("Справочник.Фирмы");
// а это – местный справочник фирм

ДокОле.ВыбратьДокументы(НачДата,КонДата);
Пока ДокОле.ПолучитьДокумент()=1 Цикл
// Ищем в местном справочнике фирм аналог фирмы
// из базы OLE (по коду):
Если Спр.найтиПоКоду(ДокОле.Фирма.Код,1)=0 Тогда
Сообщить(
"Найден документ с неизвестной фирмой!"
"Ее код: " + ДокОле.Фирма.Код+""
);
// На самом деле, она может быть просто не указана :))

Если ДокОле.Фирма.Выбран()=0 Тогда
Сообщить("Oна просто не указана! :))");
КонецЕсли;

ИначеЕсли Спр.ТекущийЭлемент()=ВыбФирма Тогда
Сообщить(
"Найден документ указанной вами фирмы!"
" № "+ ДокОле.НомерДок +" от " + ДокОле.ДатаДок+""
);
КонецЕсли;

// Ищем аналог документа в местной базе
Док.НайтиПоНомеру(ДокОле.НомерДок,ДокОле.датаДок);

Если Док.Выбран()=1 Тогда
Сообщить(
"Документ № "+Док.НомерДок + " от " +
""+Док.ДатаДок + " уже есть!"
);

Если Док.ТекущийДокумент() = ВыбДокумент Тогда
Предупреждение("Документ найден!");
КонецЕсли;

Иначе
Сообщить(
"Документ № "+ДокОле.НомерДок + " от " +
"" + ДокОле.ДатаДок + " не найден в базе!");
КонецЕсли;

КонецЦикла;

// А заодно и получим остаток товара в базе OLE:
РегОле = БазаОле.CreateObject("Регистр.ОстаткиТоваров");
ТовОле = БазаОле.CreateObject("Справочник.Номенклатура");
ТовОле.НайтиПоКоду(ВыбТовар.Код,0);
ФрмОле = БазаОле.CreateObject("Справочник.Фирмы");
ФрмОле.НайтиПоКоду(ВыбФирма.Код,0);
ОстатокТовараНаСкладеОле = РегОле.СводныйОстаток(
ТовОле.ТекущийЭлемент(),,
Фрм.ТекущийЭлемент(),
"ОстатокТовара");
Преобразование перечислений и видов субконто (подразумевается, что в обоих базах есть аналоги)
Вся задача сводится к получению строкового или числового представления перечисления или вида субконто. Не поймите это как прямую команду воспользоваться функцией Строка() или Число() ;-). Нет. Для этого у нас есть обращение к уникальному представлению перечисления и вида субконто – метод Идентификатор() или ЗначениеПоНомеру(). Второй вариант не очень подходит, так как зачастую в разных базах даже перечисления бывают расположены в другом порядке, а вот идентификаторы стараются держать одинаковыми в разных базах. Отсюда вывод, пользуйтесь идентификаторами. Кстати, не путайте вид субконто с самим субконто! Привожу пример преобразования:
// ДокОле – документ базы OLE, и
// уже спозиционирован на нужном нам документе,
// Док – документ местной базы
// (например, новый), который мы пытаемся заполнить
// реквизитами из документа базы OLE
// Будем считать, что реквизиты типа "справочник"
// мы уже перенесли :)
// Преобразуем перечисление, и не говорите потом
// "с ума сойти, как оказалось все просто!"

Если ПустоеЗначение(
ДокОле.ПризнакНакладной.Идентификатор())=0
Тогда
// если не проверим реквизит на пустое значение -
//нарвемся на ошибку V7 :(

Док.ПризнакНакладной =
Перечисление.ПризнРасхНакл.ЗначениеПоИдентификатору(
ДокОле.ПризнакНакладной.Идентификатор());
// Что и требовалось сделать

КонецЕсли;

// Преобразуем вид субконто
Если ПустоеЗначение(
ДокОле.ВидСубконтоСчетаЗатрат.Идентификатор())=0
Тогда
// если не проверим реквизит на пустое значение -
// нарвемся на ошибку V7 :(

Док. ВидСубконтоСчетаЗатрат =
ВидСубконто.ЗначениеПоИдентификатору(
ДокОле.ВидСубконтоСчетаЗатрат.Идентификатор());
// Что и требовалось сделать
КонецЕсли;
То же самое относится и к плану счетов – принцип работы с ним тот же, что и для вида субконто.

Преобразование счетов
Во многом объект Счет аналогичен объекту Справочник. Отсюда и пляшем:
Док.СчетУчета=СчетПоКоду(ДокОле.СчетУчета.Код);
// присвоили документу реквизит счета

Если
СчетПоКоду(ДокОле.СчетУчета.Код)=СчетПоКоду("10.5")
Тогда
// Это именно счет 10.5 :)
КонецЕсли;

// Я специально оставил в "Если" функцию СчетПоКоду(),
// т.к. в планах счетов разных баз
// могут быть разные форматы счетов, и простое сравнение
// кода может привести к противоположному результату.
// Кстати, и сам СчетПоКоду() тоже может иногда подвести,
// так что вам решать, каким методом пользоваться:)
Наверное, по преобразованию объектов уже хватит.

4. Работа с запросами и EvalExpr()
Наконец-то добрались и до запросов. Надо пояснить несколько вещей, касаемых запросов (да и EvalExpr() тоже). Самое главное – компиляция текста OLE-запроса (т.е. разбор всех переменных внутри запроса), как и сами OLE-запросы, выполняются внутри базы OLE и поэтому ни одна переменная, ни один реквизит местной базы там недоступны. Да и сам запрос даже не подозревает, что его запускают по OLE из другой базы! Поэтому, чтобы правильно составить текст, иногда требуется не только обдумать, как передать параметры запроса в базу OLE, но и обдумать, что нужно добавить в глобальный модуль той самой базы OLE, чтобы как-то собрать для запроса переменные!
1.   Поскольку сам текст запроса и функции EvalExpr() является по сути текстом, а не набором параметров, то напрямую передать ему ссылку на элемент справочника, документ, счет и т.п. нельзя. Исключение может быть составлено для конкретных значений перечислений, видов субконто, констант, планов счетов и т.п.
2.   Хоть и многим и так понятно, что я скажу дальше, но я все-таки уточню: при описании переменных в тексте запроса не забывайте, что объекты базы надо указывать напрямую, без всяких префиксов типа БазаОле.
3.   Отрабатывать запрос сложно тем, что ошибки, например, при компиляции напрямую не увидеть. Поэтому начинаем пошагово готовится к отработке запроса в базе OLE.
Вначале допишем в глобальном модуле базы OLE несколько строк, которые нам помогут в работе:
Перем СписокЗначенийЗапроса[10] Экспорт;
// Мы в них запихнем"значения для запроса

Функция СкорректироватьСписок(
ИндексМассива,
Действие,
ТипОбъекта = "",
ВидОбъекта = "",
Параметр1 = "",
Параметр2 = "",
Параметр3 = "",
// Ну и далее, сколько надо
// ...
Параметр99 = "") Экспорт

ИндексМассива=Число(ИндексМассива);

Если ИндексМассива = 0 Тогда
Возврат -99;
// Не указали индекс массива
КонецЕсли;

Если Действие = 1 Тогда
// Очистить список значений
СпЗапроса[ИндексМассива].УдалитьВсе();
Возврат 1;

ИначеЕсли Действие = 2 Тогда
// Добавить объект в список

Если ТипОбъекта = "Документ" Тогда

Если ВидОбъекта = "" Тогда
Возврат -99;
// Передавайте нормальный вид объекта!
КонецЕсли;

Попытка
Док = СоздатьОбъект("Документ."+ВидОбъекта);
Исключение
Возврат -99;
// Попытка обращения к неверному объекту
КонецПопытки;

Если
Док.НайтиПоНомеру(Параметр1,Параметр2)=1
Тогда
СпЗапроса[ИндексМассива].ДобавитьЗначение(
Док.ТекущийДокумент());
Возврат 1; // Нашли документ
Иначе
Возврат 0; // Не нашли документ :(
КонецЕсли;

ИначеЕсли ТипОбъекта = "Справочник" Тогда

Если ВидОбъекта = "" Тогда
Возврат -99;
// Передавайте нормальный вид объекта!
КонецЕсли;

Попытка
Спр = СоздатьОбъект("Справочник."+ВидОбъекта);
Исключение
Возврат -99;
// Попытка обращения к неверному объекту
КонецПопытки;

// Параметр1 – Код
// Параметр2 – наименование
// Параметр3 – флаг глобального поиска

Если
Спр.НайтиПоКоду(Параметр1,Число(Параметр3))=1
Тогда
СпЗапроса[ИндексМассива].ДобавитьЗначение(
Спр.ТекущийЭлемент());
Возврат 1; // Нашли элемент

ИначеЕсли
Спр.НайтиПоНаименованию(
Параметр2,
Число(Параметр3))=1
Тогда
СпЗапроса[ИндексМассива].ДобавитьЗначение(
Спр.ТекущийЭлемент());
Возврат 1; // Нашли элемент

Иначе
Возврат 0; // Не нашли элемент :(
КонецЕсли;

ИначеЕсли ТипОбъекта = "Счет" Тогда

// Вид объекта – Идентификатор плана счетов
// Параметр1 – Код счета

Если ВидОбъекта<>"" Тогда
ИскомыйСчет = СчетПоКоду(Параметр1,
ПланСчетов.ЗначениеПоИдентификатору(
ВидОбъекта));
Иначе
ИскомыйСчет = СчетПоКоду( Параметр1);
КонецЕсли;

Если ПустоеЗначение(ИскомыйСчет) = 1 Тогда
Возврат 0; // не нашли счет :(
Иначе
СпЗапроса[ИндексМассива].ДобавитьЗначение(
ИскомыйСчет);
Возврат 1; // нашли счет
КонецЕсли;

Иначе
Возврат -99;
// Неверный тип объекта
КонецЕсли;

ИначеЕсли Действие = 3 Тогда
// Вернуть размер списка
Возврат
СпЗапроса[ИндексМассива].РазмерСписка();

Иначе
Возврат -99;
// Неверное действие
КонецЕсли;

Возврат -999;
КонецФункции

Процедура ПриНачалеРаботыСистемы()

// Данная процедура уже есть в глобальном модуле,
// просто надо дописать в неё несколько строк:

Для к=1 По 10 Цикл
СпЗапроса[к]=СоздатьОбъект("СписокЗначений");
КонецЦикла;

КонецПроцедуры
Теперь начинаем потихоньку писать сам запрос. Что мы имеем? В форме диалога местной базы есть несколько реквизитов диалога (либо это будут местные переменные):
•   Даты периода (НачДата и КонДата)
•   Элементы справочников для фильтрации (ВыбТовар, ВыбФирма, ВыбКлиент, и т.д.)
•   Какие-либо флажки (ТолькоЗамерзающийЗимойТовар , ...)
Мы начинаем писать запрос и сразу попадаем в этакую ловушку:
ТекстЗапроса = " Период с Начдата по КонДата; ";
Вроде все в порядке, но такой запрос не выполнится в базе OLE, так как там понятия не имеют, что такое НачДата и КонДата. Ведь эти переменные действительны только для местной базы! Переписываем запрос заново:
ТекстЗапроса = " Период с '"+НачДата+ "' по '"+КонДата+"';
// Будет типа '01.01.02'
| Товар = Регистр.ОстаткиТоваров.Товар;
| Фирма = Регистр.ОстаткиТоваров.Фирма;
| Склад = Регистр.ОстаткиТоваров.Склад;
| Остаток = Регистр.ОстаткиТоваров.Остаток;
| Группировка Товар без групп;
| Группировка Документ;
| Функция НачОст=НачОст(Остаток);
| Функция КонОст=КонОст(Остаток);
| Функция ПрихОст=Приход(Остаток)
| Функция РасхОст=Расход(Остаток);";

Так... Дошли до условий отбора в запросе. Рассмотрим два варианта, когда выбран ВыбТовар:
// 1-й вариант, выбран элемент справочника (не группа).
// Самый простой случай - коды товаров совпадают абсолютно

// Вариант 1а.
Если ВыбТовар.Выбран()=1 Тогда
ТекстЗапроса = ТекстЗапроса +
"Условие (Товар.Код = " +ВыбТовар.Код+");";
КонецЕсли;

// Вариант 1б. Чтоб запрос работал быстрее
// Вначале добавим к запросу переменную в общем списке:
// | КодТовара = Регистр.ОстаткиТоваров.Товар.Код;
// А уж потом добавим к запросу условие (такое
// условие будет выполнятся проще, так как
// запрос при формировании таблицы запроса сразу
// сформирует отдельную колонку кодов и по ней уже
// будет отбирать, а не будет каждый раз при обработке
// товаров извлекать из них код)

Если ВыбТовар.Выбран()=1 Тогда
ТекстЗапроса = ТекстЗапроса +
"Условие (КодТовара = " +ВыбТовар.Код+");";
КонецЕсли;
Казалось бы все очень просто. По аналогии – если уникальность для товаров ведется по наименованию, то простой заменой слова «код» на слово «наименование» мы решаем вопрос и здесь. Теперь рассмотрим, когда мы выбрали группу, т.е. текст условия должен будет выглядеть так:
| Условие (товар.ПринадлежитГруппе(КакаяТоГруппа)=1);
Правда, и здесь можно проблему решить двумями путями ;-). Первый путь – когда мы имеем дело с двухуровневым справочником. Тогда проблема группы решается так же просто, как и в первом варианте:
// Добавляем в списке переменных строку:
| КодРодителя = Регистр.ОстаткиТоваров.Товар.Родитель.Код;

// Далее пишем условие:
Если ВыбТовар.Выбран()=1 Тогда
ТекстЗапроса = ТекстЗапроса +
"Условие (КодРодителя = " +ВыбТовар.Код+");";
КонецЕсли;


А если справочник очень даже многоуровневый? Вот для этого мы и используем написанную ранее
функцию. Предположим, что список значений запроса с индексом массива 1 мы будем использовать
для хранения подобных значений (например, хранить в нем группы товаров, клиентов) для
хитрых условий. Итак, например, в ВыбТовар у нас указана группа товаров, а в
ВыбКлиент – группа клиентов, которым мы товары группы ВыбТовар продавали.
Кроме того, мы должны пропустить накладные возвратов поставщикам, и не забыть, что товары
надо еще и отбирать по флажку ТолькоЗамерзающийЗимойТовар:

// Очистим список значений запроса
Результат = БазаОле.EvalExpr("СкорректироватьСписок(1,1)");

// Закинем в список значений запроса группу товаров
// (он сам найдет ее в базе OLE)
// И запоминаем (в уме), что этой группе соответствует
// 1-e значение списка

Результат = БазаОле.EvalExpr("СкорректироватьСписок(1,2,
""Справочник"", """ +
Выбтовар.Вид())+ """," +
ВыбТовар.Код + ", """ +
ВыбТовар.Наименование + """)");

// Теперь закинем в список значений запроса группу клиентов
// И запоминаем, что этой группе соответствует
// 2-е значение списка

Результат = БазаОле.EvalExpr("СкорректироватьСписок(1,2,
""Справочник"", """ +
ВыбКлиент.Вид())+ """," +
ВыбКлиент.Код + ", """ +
ВыбКлиент.Наименование + """)");

// А еще до кучи и фирму из ВыбФирма
// И запоминаем, что этой фирме соответствует
// 3-е значение списка

Результат = БазаОле.EvalExpr("СкорректироватьСписок(1,2,
""Справочник"", """ +
ВыбФирма.Вид())+ """," +
ВыбФирма.Код + ", """ +
ВыбФирма.Наименование + """)");

// Теперь формируем текст запроса

ТекстЗапроса = " Период с '"+НачДата+ "' по '"+КонДата+"';
| Товар = Документ.РасходнаяНакладная.Товар;
| Замерзает =
| Документ.РасходнаяНакладная.Товар.ЗамерзаетЗимой;
| Признак = Документ.РасходнаяНакладная.ПризнакНакладной;
| Фирма = Документ.РасходнаяНакладная.Фирма;
| Клиент = Документ.РасходнаяНакладная.Клиент;
| Количество = Документ.РасходнаяНакладная.Количество;
| СуммаДок = Документ.РасходнаяНакладная.Сумма;
| Группировка Товар без групп;
| Группировка Документ;
| Функция СуммаОтгрузки=Сумма(СуммаДок);
| Условие (Признак<>
| Перечисление.ПризнРасхНакл.ВозвратПоставщику);
| Условие (Замерзает = " + ТолькоЗамерзающийЗимойТовар + ");

// Внимание! Начинается:

| Условие (Товар.ПринадлежитГруппе(
| СпЗапроса[1].ПолучитьЗначение(1))=1);

| Условие (Клиент.ПринадлежитГруппе(
| СпЗапроса[1].ПолучитьЗначение(2))=1);
| Условие (Фирма= СпЗапроса[1].ПолучитьЗначение(3));";
Уфф. Вроде все. Остается только запустить запрос:
Запрос = БазаОле.CreateObject("Запрос");
Если Запрос.Выполнить(ТекстЗапроса)=0 Тогда
Предупреждение("Запрос безутешен!");
Возврат;
КонецЕсли;

Ну, а с реквизитами запроса разбираемся так же, как указано было выше в предыдущих разделах. И не забываем, что кроме хранения конкретных значений можно использовать другие списки значений запроса. Например, можно заполнить какой-либо список значений запроса списком клиентов и использовать его в запросе:
// Всякими правдами/неправдами заполнили список значений // (хотя бы через другой запрос :) // конкретными клиентами // Предположим, индекс массива равен "2". Тогда в тексте // запроса появится следующее: | Условие (Клиенты в СписокЗначенийЗапроса[2]);
Что осталось за бортом
Перенос реквизитов неограниченной длины, работа с периодическими реквизитами.