В этой статье мы подробно рассмотрим, как установить OLE-соединение между базами 1С 7.7 (например, из Бухгалтерии в Торговлю и Склад) для получения остатков товаров. Мы проанализируем распространенную проблему "плохого типа переменной" и предложим несколько эффективных решений, включая использование экспортных функций, метода EvalExpr() и прямого обращения к регистрам, а также дадим общие рекомендации по работе с OLE в 1С 7.7.
При попытке получить остаток напрямую через OLE-объект регистра, например, используя конструкцию:
рег = Оле.createobject(“регистр.остаткитмц»);
Остаток = рег.остаток(,спроле.текущийэлемент,,,»количество»);
мы часто сталкиваемся с ошибкой "плохой тип переменной", как это было описано в начальном сообщении. В переменной спроле содержится ссылка на элемент справочника номенклатуры, полученная из OLE-объекта.
Причина этой ошибки кроется в том, что OLE-объекты, представляющие ссылки на элементы справочников из контекста одной базы 1С (например, базы-приемника), не всегда корректно интерпретируются как ссылки в целевой базе (базе-источнике) без явного преобразования. Система 1С не может автоматически сопоставить OLE-ссылку из внешнего контекста с внутренним представлением ссылки в текущей базе.
Для получения остатка на конкретную дату и по конкретному товару, метод Регистр.Остаток() в 1С 7.7 ожидает параметры, которые должны быть распознаны в контексте целевой базы. Прямая передача OLE-объекта, представляющего собой элемент справочника, часто приводит к упомянутой ошибке. Рассмотрим, как можно обойти эту проблему.
EvalExpr()Одним из наиболее надежных и гибких способов решения проблемы "плохого типа переменной" является создание экспортной функции в глобальном модуле базы-источника (той, из которой мы хотим получить данные) и последующий вызов этой функции через метод EvalExpr() OLE-соединения. Этот подход позволяет передавать параметры в виде строк (например, коды или уникальные идентификаторы объектов), а вся логика работы с объектами и их преобразованиями происходит уже в "родной" среде вызываемой базы.
Разберем шаги по реализации этого подхода:
Пример экспортной функции для получения остатка по номенклатуре и складу. Мы можем адаптировать ее для поиска товара по коду, чтобы упростить передачу параметра из внешней базы:
// Глобальный модуль базы ТИС (источника)
// Функция для получения остатка по коду ТМЦ, коду склада и дате
Функция ПолучитьОстатокТМЦПоКоду(КодТМЦ, КодСклада = "", ДатаОстатка = "000101") Экспорт
Перем ТМЦ, Склад, Остаток;
ТМЦ = "";
Склад = "";
// Поиск элемента справочника ТМЦ по коду
Если ПустоеЗначение(КодТМЦ) = 0 Тогда
СпрТМЦ = СоздатьОбъект("Справочник.ТМЦ");
Если СпрТМЦ.НайтиПоКоду(КодТМЦ) = 1 Тогда
ТМЦ = СпрТМЦ.ТекущийЭлемент();
Иначе
Возврат 0; // Товар не найден по коду
КонецЕсли;
Иначе
Возврат 0; // Код ТМЦ не передан
КонецЕсли;
// Поиск элемента справочника Склады по коду (если КодСклада передан)
Если ПустоеЗначение(КодСклада) = 0 Тогда
СпрСклады = СоздатьОбъект("Справочник.Склады");
Если СпрСклады.НайтиПоКоду(КодСклада) = 1 Тогда
Склад = СпрСклады.ТекущийЭлемент();
КонецЕсли;
КонецЕсли;
// Установка даты расчета регистров, если она указана и не является пустой
Если ПустоеЗначение(ДатаОстатка) = 0 Тогда
Регистры.РассчитатьРегистрыНа(ДатаОстатка);
КонецЕсли;
РегОстатки = СоздатьОбъект("Регистр.ОстаткиТМЦ");
Остаток = 0;
Если ПустоеЗначение(ТМЦ) = 0 Тогда
// Получаем остаток. Параметры: (Фирма, Количество, ТМЦ, Склад, Проект, Серия...)
// Мы используем "количество" как название ресурса, а остальные параметры могут быть пустыми,
// либо заполнены, если это предусмотрено конфигурацией.
Остаток = РегОстатки.Остаток(,, ТМЦ, Склад,,"количество");
КонецЕсли;
Возврат Остаток;
КонецФункции
// Вспомогательная функция, упомянутая в сообщении форума,
// может использоваться для передачи значений между контекстами EvalExpr.
// В данном случае, ПолучитьОстатокТМЦПоКоду() возвращает значение напрямую,
// что делает ГлРавно необязательной для получения результата.
Функция ГлРавно(Получатель, Источник) Экспорт
Получатель = Источник;
Возврат ""; // EvalExpr возвращает строку, можно вернуть пустую
КонецФункции
EvalExpr() из базы-приемника: В базе, которая инициирует OLE-соединение (например, Бухгалтерия), мы можем вызвать эту экспортную функцию.Сначала нам нужно получить код товара из OLE-объекта, представляющего собой ссылку на номенклатуру в ТИС. Если спроле.текущийэлемент уже является OLE-объектом, то получить его код довольно просто:
// В базе-приемнике (Бухгалтерия)
Оле = Новый COMОбъект("V77.Application");
// Здесь мы предполагаем, что Оле уже подключен к базе ТИС.
// Подключение выглядит примерно так:
// Оле.Initialize(Оле.RM_Exclusive, "/D " + ПутьКБазеТИС + " /N" + ИмяПользователя + " /P" + Пароль, "NO_SPLASH_SHOW");
// Оле.OpenDataBase();
// Пусть СпрОЛЕ.ТекущийЭлемент() - это OLE-объект элемента справочника номенклатуры из ТИС,
// который мы получили ранее или передали как параметр.
// Например, если мы итерируем по справочнику номенклатуры в Бухгалтерии:
// СпрБ = СоздатьОбъект("Справочник.Номенклатура");
// СпрБ.ВыбратьЭлементы();
// Пока СпрБ.ПолучитьЭлемент() = 1 Цикл
// КодТМЦИзБух = СпрБ.Код; // Получаем код номенклатуры из текущей (Бухгалтерской) базы
// Для примера возьмем некий код товара и склада.
КодТМЦ = "00001"; // Код товара, который нам нужно найти в ТИС
КодСклада = "01"; // Код склада в ТИС (если нужен фильтр по складу)
ДатаЗапроса = ТекущаяДата(); // Текущая дата
ФорматДаты = Формат(ДатаЗапроса, "ДДММГГ"); // Форматируем дату для передачи в 1С 7.7
// Формируем строку выражения для EvalExpr.
// Необходимо экранировать кавычки, если они используются внутри строки.
// В 1С 7.7 для этого используется две кавычки подряд ("").
ВыражениеДляВычисления = "ПолучитьОстатокТМЦПоКоду(""" + КодТМЦ + """, """ + КодСклада + """, """ + ФорматДаты + """)";
// Вызываем экспортную функцию через EvalExpr
Результат_Строка = Оле.EvalExpr(ВыражениеДляВычисления);
// EvalExpr всегда возвращает строку, поэтому преобразуем ее в число.
Остаток = Число(Результат_Строка);
Сообщить("Остаток товара '" + КодТМЦ + "' на " + Формат(ДатаЗапроса, "ДФ=dd.MM.yyyy") + ": " + Остаток);
// КонецЦикла;
Важно: EvalExpr() всегда возвращает строковое значение. Если ожидается число, дата или булево, необходимо выполнить явное преобразование типов после получения результата. Также обратите внимание на правильное экранирование кавычек в строке, передаваемой в EvalExpr().
Функция ГлРавно(), упомянутая в сообщениях форума, может быть использована для присвоения значений переменным в контексте OLE-сервера. Однако, для получения результата напрямую из функции ПолучитьОстатокТМЦПоКоду(), ее использование не является обязательным, так как функция уже возвращает необходимое значение. Если же вам нужно присвоить результат некой глобальной переменной в OLE-базе, тогда:
// Пример использования ГлРавно (как на форуме)
// В этом случае "остаток" будет переменной в контексте OLE-базы
Оле.EvalExpr("ГлРавно(остатокПеременнаяВТИС, Регистры.ОстаткиТМЦ.Остаток(,, Справочники.ТМЦ.НайтиПоКоду(""" + КодТМЦ + """), Справочники.Склады.НайтиПоКоду(""" + КодСклада + """),,""количество""))");
Остаток = Число(Оле.EvalExpr("остатокПеременнаяВТИС"));
Сообщить("Остаток товара через ГлРавно: " + Остаток);
Использование экспортных функций, которые инкапсулируют логику получения данных, является наиболее рекомендуемым подходом, поскольку это позволяет управлять всеми преобразованиями типов и поиском объектов в родной среде 1С 7.7, минимизируя проблемы с совместимостью OLE-объектов.
УстановитьФильтр() и ВыгрузитьИтоги()Другой подход, который был рассмотрен в обсуждении, заключается в прямом обращении к объекту регистра в целевой базе через OLE и использовании его методов для получения итогов. Этот способ может быть полезен, если нам нужно получить не один остаток, а таблицу с итогами по нескольким позициям или параметрам.
Оле = Новый COMОбъект("V77.Application");
// ... (подключение к базе ТИС)
РегОле = Оле.CreateObject("Регистр.ОстаткиТМЦ");
УстановитьФильтр() предназначен для отбора данных. Мы можем попытаться установить фильтр по товару или другим измерениям регистра.
// Пусть СпрОЛЕ.ТекущийЭлемент() - это OLE-объект элемента справочника номенклатуры из ТИС.
// В исходном сообщении была попытка: РегОле.УстановитьФильтр("ТМЦ", олетмц);
// Как было замечено, эта конструкция может выдать ошибку "плохой тип переменной".
// Это происходит потому, что "олетмц" (OLE-объект из внешнего контекста)
// не всегда корректно преобразуется в ссылку в контексте регистра в другой базе.
Если УстановитьФильтр() выдает ошибку, можно попробовать установить фильтр через EvalExpr(), где вся работа с преобразованием объекта происходит в целевой базе. Для этого нам понадобится код или уникальный идентификатор товара:
КодТМЦДляФильтра = "00001"; // Код товара для установки фильтра
КодСкладаДляФильтра = "01"; // Код склада для установки фильтра
// Устанавливаем дату расчета регистров перед фильтрацией
ДатаЗапроса = ТекущаяДата();
ФорматДаты = Формат(ДатаЗапроса, "ДДММГГ");
Оле.EvalExpr("Регистры.РассчитатьРегистрыНа(""" + ФорматДаты + """);");
// Установка фильтра по товару и складу через EvalExpr
// Здесь мы ищем объекты по коду уже в контексте OLE-базы
Оле.EvalExpr("Регистры.ОстаткиТМЦ.УстановитьФильтр(""ТМЦ"", Справочники.ТМЦ.НайтиПоКоду(""" + КодТМЦДляФильтра + """));");
Оле.EvalExpr("Регистры.ОстаткиТМЦ.УстановитьФильтр(""Склад"", Справочники.Склады.НайтиПоКоду(""" + КодСкладаДляФильтра + """));");
Важно: УстановитьФильтр() возвращает булево значение (успех/неуспех), но через EvalExpr() его результат можно игнорировать или обработать, если функция просто выполняется для установки фильтра.
ВыгрузитьИтоги() без фильтра выгрузит все итоги регистра. Это может привести к большому объему данных и необходимости ручной фильтрации уже в приемной базе.
// Создаем OLE-объект ТаблицаЗначений в контексте OLE-сервера
ТЗ_Остатки = Оле.CreateObject("ТаблицаЗначений");
// Выгружаем итоги регистра в эту таблицу
РегОле.ВыгрузитьИтоги(ТЗ_Остатки);
// Для отладки и проверки количества строк в полученной таблице значений,
// как было рекомендовано в сообщениях форума:
Сообщить("Количество строк в таблице итогов: " + ТЗ_Остатки.КоличествоСтрок());
// Переменные для хранения найденного остатка
ОстатокПоИскомомуТовару = 0;
ТоварНайден = 0;
Пока ТЗ_Остатки.ПолучитьСтроку() = 1 Цикл
// Предполагаем, что колонка "ТМЦ" содержит ссылку на товар, "Количество" - остаток
// и другие колонки могут присутствовать (например, "Склад").
// Сравнение OLE-объектов по их свойствам (например, по коду),
// так как прямое сравнение ссылок между OLE-контекстами может быть проблематичным.
Если ТЗ_Остатки.ТМЦ.Код = КодТМЦДляФильтра Тогда
// Если также фильтровали по складу, можем добавить проверку и здесь
Если (КодСкладаДляФильтра = "") ИЛИ (ТЗ_Остатки.Склад.Код = КодСкладаДляФильтра) Тогда
ОстатокПоИскомомуТовару = ТЗ_Остатки.Количество;
Сообщить("Найден остаток для товара '" + ТЗ_Остатки.ТМЦ.Наименование +
"' на складе '" + ТЗ_Остатки.Склад.Наименование +
"': " + ОстатокПоИскомомуТовару);
ТоварНайден = 1;
Прервать; // Выходим из цикла, если нашли нужный остаток
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если ТоварНайден = 0 Тогда
Сообщить("Остаток для товара '" + КодТМЦДляФильтра + "' не найден.");
КонецЕсли;
Важно помнить, что если в конфигурации ведется партионный учет, то для одного и того же товара может быть несколько партий, что приведет к нескольким строкам итогов для одной номенклатуры. В этом случае может потребоваться дополнительная логика для суммирования остатков.
Для получения остатков на определенную дату, помимо возможной установки даты в экспортной функции (как показано в Решении 1), мы можем использовать метод РассчитатьРегистрыНа(<Дата>) для объекта регистра. Этот метод позволяет установить контекст получения итогов на заданную дату, после чего все последующие обращения к итогам регистра будут учитывать эту дату.
// Устанавливаем OLE-соединение и создаем объект регистра, как показано выше
// ...
РегОле = Оле.CreateObject("Регистр.ОстаткиТМЦ");
// Пример даты, на которую нужно получить остатки
ДатаРасчета = "20231231"; // Дата в формате ГГГГММДД
// Вызываем метод РассчитатьРегистрыНа через EvalExpr,
// чтобы он выполнился в контексте целевой базы
Оле.EvalExpr("Регистры.ОстаткиТМЦ.РассчитатьРегистрыНа(""" + ДатаРасчета + """);");
// Теперь все последующие вызовы методов РегОле (например, Остаток() или ВыгрузитьИтоги())
// будут возвращать данные на указанную дату.
// Например, если КодТМЦ и КодСклада уже определены:
ОстатокНаДату = Число(Оле.EvalExpr("Регистры.ОстаткиТМЦ.Остаток(,, Справочники.ТМЦ.НайтиПоКоду(""" + КодТМЦ + """), Справочники.Склады.НайтиПоКоду(""" + КодСклада + """),,""количество""));"));
Сообщить("Остаток на " + Формат(Дата(Лев(ДатаРасчета,4), Сред(ДатаРасчета,5,2), Прав(ДатаРасчета,2)), "ДФ=dd.MM.yyyy") + ": " + ОстатокНаДату);
После вызова РассчитатьРегистрыНа(), методы получения итогов (например, Регистр.Остаток() или ВыгрузитьИтоги()) будут работать с данными, актуальными на эту дату, пока не будет вызван другой РассчитатьРегистрыНа() или не будет установлен стандартный контекст по текущей дате.
Работа с OLE в 1С 7.7 имеет свои особенности, и мы должны учитывать несколько ключевых моментов для успешной интеграции:
ЗначениеВСтрокуВнутр(Объект). Например, Оле.EvalExpr("ЗначениеВСтрокуВнутр(ТвойОбъект)").EvalExpr() всегда возвращает строку. Выполняйте явное преобразование типов (Число(), Дата(), Булево()) в вашей базе-приемнике по необходимости, чтобы корректно использовать полученные данные.Сообщить() в сочетании с EvalExpr() для вывода значений переменных или результатов выражений непосредственно из контекста OLE-сервера. Это поможет вам понять, какие значения принимают переменные и как выполняются операции в удаленной базе. Например: Сообщить(Оле.EvalExpr("Справочники.ТМЦ.НайтиПоКоду(""" + КодТМЦ + """)")); поможет увидеть, найден ли объект.ПустоеЗначение() или ПустаяСтрока(). При работе с CreateObject() или вызове методов OLE, используйте конструкцию Попытка...Исключение для обработки возможных ошибок OLE и предотвращения аварийного завершения работы программы.EvalExpr() для каждого отдельного товара или параметра. Минимизация количества OLE-вызовов сокращает накладные расходы на межпроцессное взаимодействие.COMОбъект("V77.Application").Применение этих подходов и рекомендаций поможет вам успешно получать данные из 1С 7.7 через OLE-соединение, избегая распространенных ошибок, связанных с типами переменных, и обеспечивая стабильную работу интеграции. Всегда выбирайте метод, который лучше всего соответствует вашей задаче, требованиям к производительности и объему передаваемых данных.