Как правильно обработать документы и изменить их реквизиты в 1С 8.3: разбор ошибок и оптимизация кода

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

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

Разбор типичных ошибок в исходном коде

Рассмотрим исходную ситуацию. У нас есть задача: отобрать документы за определенный период по списку клиентов, у которых статус не равен «Выполнено», и программно установить им этот статус. В представленном на форуме коде допущено несколько критических ошибок, которые приведут к неработоспособности программы.

Ошибка №1: Контекст выполнения. В коде процедура объявлена с директивой &НаКлиенте. В этой области памяти объект Запрос недоступен. Платформа выдаст ошибку «Тип не определен», так как работа с базой данных (запросы, выборки, изменение объектов) возможна только на стороне сервера. Проанализируем ситуацию: клиентское приложение отвечает за интерфейс, а сервер — за тяжелые операции с данными.

Ошибка №2: Синтаксис запроса. В тексте запроса отсутствуют запятые между выбираемыми полями, неверно указаны источники данных и условия. Кроме того, использование виртуальных таблиц требует передачи параметров непосредственно в саму таблицу для обеспечения производительности.

Ошибка №3: Попытка изменения ссылки вместо объекта. Это классическая ошибка. Программист пытается изменить реквизит у переменной стр.ДокНазначения. Однако в выборке запроса мы получаем ссылку на документ. Ссылка — это всего лишь уникальный идентификатор записи в базе данных, она доступна только для чтения. Для изменения данных необходимо получить сам объект документа.

Шаг 1: Правильная организация контекста и передача параметров

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


&НаСервере
Процедура ВыполнитьОбработкуНаСервере(ДатаНачала, ДатаОкончания, СписокКлиентов)
    // Здесь будет основной код работы с запросом и документами
КонецПроцедуры

Обратите внимание, что если СписокКлиентов является объектом типа СписокЗначений, его можно напрямую передавать в параметры запроса. Платформа 1С самостоятельно преобразует его в условие В (...) для SQL-запроса.

Шаг 2: Оптимизация запроса и использование виртуальных таблиц

Одной из главных рекомендаций опытных разработчиков является перенос всех фильтров внутрь запроса. В исходном коде автор использовал метод СписокКлиентов.НайтиПоЗначению() внутри цикла. Это крайне неэффективно: если в результате запроса будет 10 000 строк, программа 10 000 раз будет выполнять поиск по списку в оперативной памяти.

Проанализируем правильный подход: использование параметров виртуальной таблицы. Если мы обращаемся к РегистрНакопления.ОборотыПоНазначениям.Обороты, все отборы (по периоду, клиенту и статусу) нужно передавать в скобках. Это позволит СУБД (SQL серверу) отсечь ненужные записи еще на этапе чтения с диска, что в десятки раз ускорит работу.


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	ОборотыПоНазначениямОбороты.ДокНазначения КАК ДокНазначения
    |ИЗ
    |	РегистрНакопления.ОборотыПоНазначениям.Обороты(&ДатаНачала, &ДатаОкончания, , 
    |       Клиент В (&СписокКлиентов) И СтатусНазначения <> &СтатусНазначения) КАК ОборотыПоНазначениямОбороты";

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаОкончания));
Запрос.УстановитьПараметр("СписокКлиентов", СписокКлиентов);
Запрос.УстановитьПараметр("СтатусНазначения", Перечисления.СтатусыНазначений.Выполнено);

Важный момент: использование ключевого слова ВЫБРАТЬ РАЗЛИЧНЫЕ. Как справедливо заметили участники обсуждения, один документ может делать несколько движений (строк) в регистре. Без этого слова мы получим дубли ссылок и будем пытаться записать один и тот же документ несколько раз, что вызовет лишнюю нагрузку или ошибки блокировок.

Шаг 3: Программное изменение и запись объектов

Теперь перейдем к циклу обработки результатов. Как мы выяснили ранее, нам необходимо использовать метод ПолучитьОбъект(). Однако стоит помнить о безопасности: перед изменением объекта желательно проверить, не был ли он удален или изменен другим пользователем.

Рассмотрим процесс по шагам:

  1. Выполняем запрос и получаем выборку.
  2. В цикле обходим каждую ссылку.
  3. Получаем объект документа через Выборка.ДокНазначения.ПолучитьОбъект().
  4. Меняем значение реквизита.
  5. Записываем объект с использованием нужного режима записи.

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    
    ДокументОбъект = Выборка.ДокНазначения.ПолучитьОбъект();
    
    Если ДокументОбъект <> Неопределено Тогда
        ДокументОбъект.СтатусНазначения = Перечисления.СтатусыНазначений.Выполнено;
        
        Попытка
            // Если документ должен быть проведен, используем РежимЗаписиДокумента.Проведение
            ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
        Исключение
            ЗаписьЖурналаРегистрации("Ошибка записи документа", УровеньЖурналаРегистрации.Ошибка, , , 
                ОписаниеОшибки());
        КонецПопытки;
    КонецЕсли;
    
КонецЦикла;

Продвинутые советы по производительности

Выясним причину, по которой опытные программисты часто критикуют запись объектов в цикле. Каждая команда Записать() открывает отдельную транзакцию в базе данных. Если документов много, это может «положить» сервер. Чтобы избежать этого, рассмотрим два улучшения:

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


НачатьТранзакцию();
Попытка
    // Цикл обработки здесь...
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение "Произошла ошибка при массовом обновлении документов!";
КонецПопытки;

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

3. Использование ОбменДанными.Загрузка. Если при смене статуса вам не нужно, чтобы срабатывали дополнительные проверки в модуле объекта (например, проверка остатков или запрет редактирования задним числом), можно установить свойство ДокументОбъект.ОбменДанными.Загрузка = Истина. Это существенно ускорит процесс записи, так как платформа пропустит большинство стандартных обработчиков событий.

Заключение

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

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

← На главную