Как программно заполнить строку с типом План Счетов и избежать ошибок контекста?

Программист 1С v8.3 (Управляемые формы) Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При программном создании документов, таких как «Реализация товаров и услуг» или любых других, затрагивающих бухгалтерский учет, часто возникает задача автоматического заполнения счетов учета (доходов, расходов, учета номенклатуры). Казалось бы, задача тривиальная — найти нужный счет и присвоить его реквизиту. Однако на практике разработчики сталкиваются с неочевидными ошибками компиляции, особенно при разработке расширений, которые могут использоваться в разных средах (включая мобильные платформы).

В этой статье мы подробно разберем, как правильно обращаться к планам счетов, почему возникает ошибка «Переменная не определена» даже при верном синтаксисе, и как оптимизировать код для повышения производительности.

1. Синтаксис обращения к Планам счетов

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

Если нам нужно найти счет (получить ссылку на него), мы должны обращаться к глобальному контексту через менеджер ПланыСчетов (во множественном числе). Часто по аналогии с Справочники или Документы разработчики пишут правильно, но иногда случаются опечатки.

Неправильно:


// Ошибка: Переменная не определена (ПланСчетов)
Строка.СчетДоходов = ПланСчетов.Хозрасчетный.НайтиПоКоду("90.01.1");

Правильно:


// Корректное обращение к коллекции менеджеров
Строка.СчетДоходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1");

Однако, даже исправив эту опечатку, мы можем столкнуться со следующей, более хитрой проблемой, связанной с контекстом выполнения кода.

2. Ошибка контекста: Мобильное приложение-сервер

Рассмотрим ситуацию, когда синтаксис верен, но платформа выдает ошибку при сохранении или проверке модуля:


{...Форма(177,30)}: Переменная не определена (ПланыСчетов)
СтрокаТЧ.СчетДоходов = <>ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1"); 
(Проверка: Мобильное приложение-сервер)

Обратите внимание на пометку в скобках: Проверка: Мобильное приложение-сервер. Это ключевой момент.

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

Даже если ваш код фактически будет выполняться только на сервере предприятия (где планы счетов есть), компилятор проверяет его валидность для всех активных контекстов, включая мобильный сервер.

3. Решение проблемы с помощью директив препроцессора

Чтобы исключить код, который не должен (и не может) работать в мобильном контексте, но необходим на "большом" сервере, мы используем инструкции препроцессора. Это специальные команды, которые говорят компилятору: "Этот кусок кода читай только если мы находимся на полноценном сервере".

Обернем проблемный участок кода (или всю процедуру, если она серверная) в инструкцию #Если Сервер Тогда.


#Если Сервер Тогда
    // Этот код будет виден компилятору только в контексте полноценного сервера
    СтрокаТЧ.СчетДоходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1");
    СтрокаТЧ.СчетРасходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.02.1");
#КонецЕсли

В случае с процедурой, которая уже помечена директивой компиляции &НаСервере, добавление инструкций препроцессора внутри может показаться избыточным, но это необходимо именно для прохождения синтаксического контроля для разных платформ назначения.

Если же вся процедура предназначена только для сервера и не нужна на мобильном, можно обернуть всю процедуру:


#Если Сервер Тогда

&НаСервере
Процедура СоздатьРеализацииПоКлиентам()
    // Весь код процедуры
    // ...
    СтрокаТЧ.СчетДоходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1");
КонецПроцедуры

#КонецЕсли

4. Оптимизация производительности: поиск вне цикла

В исходном коде, представленном в обсуждении, есть серьезная архитектурная ошибка, которая может существенно замедлить работу при большом количестве обрабатываемых данных. Подобные проблемы часто становятся объектом для поиска узких мест в производительности и требуют особого внимания. Давайте проанализируем этот фрагмент:


Пока Результат1.Следующий() Цикл
    // ... создание документа ...
    СтрокаТЧ.СчетДоходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1");
    СтрокаТЧ.СчетРасходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.02.1");
КонецЦикла;

В чем проблема?
Метод НайтиПоКоду() выполняет запрос к базе данных. Если в выборке Результат1 будет 1000 строк, система сделает 2000 лишних запросов к базе данных (по 2 запроса на каждую итерацию), чтобы найти одни и те же счета. Это одна из самых частых ошибок производительности при работе в цикле, которую легко выявить в процессе проведения Code-review.

Как сделать правильно?
Необходимо найти ссылки на счета один раз перед началом цикла, сохранить их в переменные и использовать внутри цикла уже готовые значения.

Рассмотрим оптимизированный и исправленный вариант кода:


&НаСервере
Процедура СоздатьРеализацииПоКлиентам()

    // 1. Используем препроцессор для защиты от ошибок компиляции в мобильном контексте
    #Если Сервер Тогда
        
        // 2. Оптимизация: Находим счета один раз ДО цикла
        Счет90_01_1 = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1");
        Счет90_02_1 = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.02.1");
        
        // Проверка, что счета найдены (хорошая практика, соответствующая стандартам и методикам разработки)
        Если Счет90_01_1.Пустая() Или Счет90_02_1.Пустая() Тогда
             // Обработка ошибки или запись в журнал регистрации
             Возврат; 
        КонецЕсли;

        // ... код выборки данных ...

        Пока Результат.Следующий() Цикл
             // ...
             Пока Результат1.Следующий() Цикл
                 // Создание документа и строки
                 СтрокаТЧ = НовыйДок.Услуги.Добавить();
                 // ... заполнение других реквизитов ...
                 
                 // 3. Присваиваем уже найденные значения
                 СтрокаТЧ.СчетДоходов = Счет90_01_1;
                 СтрокаТЧ.СчетРасходов = Счет90_02_1;
                 
                 НовыйДок.Записать();
             КонецЦикла;
        КонецЦикла;

    #КонецЕсли

КонецПроцедуры

Такой подход не только решает локальную задачу, но и является частью общих стандартов при разработке компонентов системы для высоконагруженных систем, где минимизация обращений к СУБД — ключевой принцип.

5. Важные нюансы при работе с расширениями

Если вы работаете в расширении конфигурации (Extension), стоит помнить о нескольких моментах, связанных с заимствованием объектов и решением типовых проблем:

  1. Заимствование типа: Если реквизиты СчетДоходов и СчетРасходов добавлены в табличную часть документа именно в расширении, убедитесь, что их тип данных (ПланСчетовСсылка.Хозрасчетный) корректно определен.
  2. Заимствование предопределенных элементов: Если вместо НайтиПоКоду вы захотите обратиться к счету через имя предопределенного элемента (например, ПланыСчетов.Хозрасчетный.Выручка), этот предопределенный счет должен быть добавлен (заимствован) в расширение. Метод НайтиПоКоду работает без заимствования конкретного счета, но он менее надежен, так как код счета может быть изменен пользователем в режиме «1С:Предприятие».

Резюме

Для успешного решения задачи программного заполнения счетов:

← На главную