Как получить остатки товаров из 1С 7.7 через OLE-соединение и решить проблему с "плохим типом переменной"?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

В этой статье мы подробно рассмотрим, как установить OLE-соединение между базами 1С 7.7 (например, из Бухгалтерии в Торговлю и Склад) для получения остатков товаров. Мы проанализируем распространенную проблему "плохого типа переменной" и предложим несколько эффективных решений, включая использование экспортных функций, метода EvalExpr() и прямого обращения к регистрам, а также дадим общие рекомендации по работе с OLE в 1С 7.7.

Основная проблема: "Плохой тип переменной" при прямом доступе к регистру

При попытке получить остаток напрямую через OLE-объект регистра, например, используя конструкцию:


рег = Оле.createobject(“регистр.остаткитмц»);
Остаток = рег.остаток(,спроле.текущийэлемент,,,»количество»);

мы часто сталкиваемся с ошибкой "плохой тип переменной", как это было описано в начальном сообщении. В переменной спроле содержится ссылка на элемент справочника номенклатуры, полученная из OLE-объекта.

Причина этой ошибки кроется в том, что OLE-объекты, представляющие ссылки на элементы справочников из контекста одной базы 1С (например, базы-приемника), не всегда корректно интерпретируются как ссылки в целевой базе (базе-источнике) без явного преобразования. Система 1С не может автоматически сопоставить OLE-ссылку из внешнего контекста с внутренним представлением ссылки в текущей базе.

Для получения остатка на конкретную дату и по конкретному товару, метод Регистр.Остаток() в 1С 7.7 ожидает параметры, которые должны быть распознаны в контексте целевой базы. Прямая передача OLE-объекта, представляющего собой элемент справочника, часто приводит к упомянутой ошибке. Рассмотрим, как можно обойти эту проблему.

Решение 1: Использование экспортных функций в целевой базе и метода EvalExpr()

Одним из наиболее надежных и гибких способов решения проблемы "плохого типа переменной" является создание экспортной функции в глобальном модуле базы-источника (той, из которой мы хотим получить данные) и последующий вызов этой функции через метод EvalExpr() OLE-соединения. Этот подход позволяет передавать параметры в виде строк (например, коды или уникальные идентификаторы объектов), а вся логика работы с объектами и их преобразованиями происходит уже в "родной" среде вызываемой базы.

Разберем шаги по реализации этого подхода:

  1. Создание экспортной функции в базе-источнике: Откроем конфигурацию базы 1С 7.7, из которой мы хотим получить остатки (например, ТИС). В глобальном модуле этой базы создадим экспортную функцию, которая будет принимать необходимые параметры (например, код или уникальный идентификатор номенклатуры и, опционально, дату и склад) и возвращать остаток.
  2. Пример экспортной функции для получения остатка по номенклатуре и складу. Мы можем адаптировать ее для поиска товара по коду, чтобы упростить передачу параметра из внешней базы:

    
    // Глобальный модуль базы ТИС (источника)
    
    // Функция для получения остатка по коду ТМЦ, коду склада и дате
    Функция ПолучитьОстатокТМЦПоКоду(КодТМЦ, КодСклада = "", ДатаОстатка = "000101") Экспорт
        Перем ТМЦ, Склад, Остаток;
        ТМЦ = "";
        Склад = "";
    
        // Поиск элемента справочника ТМЦ по коду
        Если ПустоеЗначение(КодТМЦ) = 0 Тогда
            СпрТМЦ = СоздатьОбъект("Справочник.ТМЦ");
            Если СпрТМЦ.НайтиПоКоду(КодТМЦ) = 1 Тогда
                ТМЦ = СпрТМЦ.ТекущийЭлемент();
            Иначе
                Возврат 0; // Товар не найден по коду
            КонецЕсли;
        Иначе
            Возврат 0; // Код ТМЦ не передан
        КонецЕсли;
    
        // Поиск элемента справочника Склады по коду (если КодСклада передан)
        Если ПустоеЗначение(КодСклада) = 0 Тогда
            СпрСклады = СоздатьОбъект("Справочник.Склады");
            Если СпрСклады.НайтиПоКоду(КодСклада) = 1 Тогда
                Склад = СпрСклады.ТекущийЭлемент();
            КонецЕсли;
        КонецЕсли;
    
        // Установка даты расчета регистров, если она указана и не является пустой
        Если ПустоеЗначение(ДатаОстатка) = 0 Тогда
            Регистры.РассчитатьРегистрыНа(ДатаОстатка);
        КонецЕсли;
    
        РегОстатки = СоздатьОбъект("Регистр.ОстаткиТМЦ");
        Остаток = 0;
    
        Если ПустоеЗначение(ТМЦ) = 0 Тогда
            // Получаем остаток. Параметры: (Фирма, Количество, ТМЦ, Склад, Проект, Серия...)
            // Мы используем "количество" как название ресурса, а остальные параметры могут быть пустыми,
            // либо заполнены, если это предусмотрено конфигурацией.
            Остаток = РегОстатки.Остаток(,, ТМЦ, Склад,,"количество"); 
        КонецЕсли;
    
        Возврат Остаток;
    КонецФункции
    
    // Вспомогательная функция, упомянутая в сообщении форума, 
    // может использоваться для передачи значений между контекстами EvalExpr.
    // В данном случае, ПолучитьОстатокТМЦПоКоду() возвращает значение напрямую, 
    // что делает ГлРавно необязательной для получения результата.
    Функция ГлРавно(Получатель, Источник) Экспорт
        Получатель = Источник;
        Возврат ""; // EvalExpr возвращает строку, можно вернуть пустую
    КонецФункции
    
  3. Вызов функции через EvalExpr() из базы-приемника: В базе, которая инициирует OLE-соединение (например, Бухгалтерия), мы можем вызвать эту экспортную функцию.
  4. Сначала нам нужно получить код товара из 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-объектов.

Решение 2: Использование методов УстановитьФильтр() и ВыгрузитьИтоги()

Другой подход, который был рассмотрен в обсуждении, заключается в прямом обращении к объекту регистра в целевой базе через OLE и использовании его методов для получения итогов. Этот способ может быть полезен, если нам нужно получить не один остаток, а таблицу с итогами по нескольким позициям или параметрам.

  1. Создание объекта регистра: Мы создадим OLE-объект регистра остатков в базе-источнике через OLE-соединение.
  2. 
    Оле = Новый COMОбъект("V77.Application");
    // ... (подключение к базе ТИС)
    РегОле = Оле.CreateObject("Регистр.ОстаткиТМЦ");
    
  3. Установка фильтра (с нюансами): Метод УстановитьФильтр() предназначен для отбора данных. Мы можем попытаться установить фильтр по товару или другим измерениям регистра.
  4. 
    // Пусть СпрОЛЕ.ТекущийЭлемент() - это OLE-объект элемента справочника номенклатуры из ТИС.
    // В исходном сообщении была попытка: РегОле.УстановитьФильтр("ТМЦ", олетмц);
    // Как было замечено, эта конструкция может выдать ошибку "плохой тип переменной".
    // Это происходит потому, что "олетмц" (OLE-объект из внешнего контекста)
    // не всегда корректно преобразуется в ссылку в контексте регистра в другой базе.
    

    Если УстановитьФильтр() выдает ошибку, можно попробовать установить фильтр через EvalExpr(), где вся работа с преобразованием объекта происходит в целевой базе. Для этого нам понадобится код или уникальный идентификатор товара:

    
    КодТМЦДляФильтра = "00001"; // Код товара для установки фильтра
    КодСкладаДляФильтра = "01"; // Код склада для установки фильтра
    
    // Устанавливаем дату расчета регистров перед фильтрацией
    ДатаЗапроса = ТекущаяДата();
    ФорматДаты = Формат(ДатаЗапроса, "ДДММГГ");
    Оле.EvalExpr("Регистры.РассчитатьРегистрыНа(""" + ФорматДаты + """);");
    
    // Установка фильтра по товару и складу через EvalExpr
    // Здесь мы ищем объекты по коду уже в контексте OLE-базы
    Оле.EvalExpr("Регистры.ОстаткиТМЦ.УстановитьФильтр(""ТМЦ"", Справочники.ТМЦ.НайтиПоКоду(""" + КодТМЦДляФильтра + """));");
    Оле.EvalExpr("Регистры.ОстаткиТМЦ.УстановитьФильтр(""Склад"", Справочники.Склады.НайтиПоКоду(""" + КодСкладаДляФильтра + """));");
    

    Важно: УстановитьФильтр() возвращает булево значение (успех/неуспех), но через EvalExpr() его результат можно игнорировать или обработать, если функция просто выполняется для установки фильтра.

  5. Выгрузка итогов в таблицу значений: После (потенциальной) установки фильтра, мы можем выгрузить итоги регистра в таблицу значений. Как было замечено в обсуждении, если фильтр не устанавливается или работает некорректно, ВыгрузитьИтоги() без фильтра выгрузит все итоги регистра. Это может привести к большому объему данных и необходимости ручной фильтрации уже в приемной базе.
  6. 
    // Создаем OLE-объект ТаблицаЗначений в контексте OLE-сервера
    ТЗ_Остатки = Оле.CreateObject("ТаблицаЗначений");
    // Выгружаем итоги регистра в эту таблицу
    РегОле.ВыгрузитьИтоги(ТЗ_Остатки);
    
    // Для отладки и проверки количества строк в полученной таблице значений,
    // как было рекомендовано в сообщениях форума:
    Сообщить("Количество строк в таблице итогов: " + ТЗ_Остатки.КоличествоСтрок());
    
  7. Итерация по таблице значений: После выгрузки мы можем перебрать строки таблицы значений и получить нужные данные.
  8. 
    // Переменные для хранения найденного остатка
    ОстатокПоИскомомуТовару = 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

Работа с OLE в 1С 7.7 имеет свои особенности, и мы должны учитывать несколько ключевых моментов для успешной интеграции:

  1. Передача идентификаторов вместо объектов: Вместо попыток передавать прямые OLE-объекты ссылок из одной базы в другую, всегда старайтесь передавать уникальные идентификаторы (УИД) или коды объектов в виде строк. В целевой базе уже по этим строковым значениям ищите нужные объекты. Для получения УИД объекта в строковом виде можно использовать функцию ЗначениеВСтрокуВнутр(Объект). Например, Оле.EvalExpr("ЗначениеВСтрокуВнутр(ТвойОбъект)").
  2. Обработка возвращаемых значений: Помните, что метод EvalExpr() всегда возвращает строку. Выполняйте явное преобразование типов (Число(), Дата(), Булево()) в вашей базе-приемнике по необходимости, чтобы корректно использовать полученные данные.
  3. Детальная отладка: Для эффективной отладки используйте функцию Сообщить() в сочетании с EvalExpr() для вывода значений переменных или результатов выражений непосредственно из контекста OLE-сервера. Это поможет вам понять, какие значения принимают переменные и как выполняются операции в удаленной базе. Например: Сообщить(Оле.EvalExpr("Справочники.ТМЦ.НайтиПоКоду(""" + КодТМЦ + """)")); поможет увидеть, найден ли объект.
  4. Проверка существования объектов и обработка ошибок: Перед использованием OLE-объектов, всегда проверяйте их на ПустоеЗначение() или ПустаяСтрока(). При работе с CreateObject() или вызове методов OLE, используйте конструкцию Попытка...Исключение для обработки возможных ошибок OLE и предотвращения аварийного завершения работы программы.
  5. Оптимизация производительности: Для больших объемов данных или частых запросов, создание одной экспортной функции в глобальном модуле OLE-базы, которая принимает список параметров (например, массив кодов товаров) и возвращает всю таблицу значений с остатками, будет значительно более производительным, чем многократные вызовы EvalExpr() для каждого отдельного товара или параметра. Минимизация количества OLE-вызовов сокращает накладные расходы на межпроцессное взаимодействие.
  6. Корректная регистрация OLE-сервера: Убедитесь, что 1С 7.7 корректно зарегистрирована в системе как OLE-сервер. Иногда для этого требуется запуск 1С с административными правами для выполнения регистрации COM-объектов в реестре Windows. Проблемы с регистрацией могут проявляться как ошибки при попытке создания COMОбъект("V77.Application").

Применение этих подходов и рекомендаций поможет вам успешно получать данные из 1С 7.7 через OLE-соединение, избегая распространенных ошибок, связанных с типами переменных, и обеспечивая стабильную работу интеграции. Всегда выбирайте метод, который лучше всего соответствует вашей задаче, требованиям к производительности и объему передаваемых данных.

← На главную