Почему не доступен реквизит на запись в списке документов 1С и как правильно вывести данные из регистра?

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

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

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

---

Причина №1: Попытка записи данных напрямую в ссылку (Ошибка «Поле объекта недоступно для записи»)

Рассмотрим исходный код, который вызвал ошибку у автора темы:


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    МассивКлючей = Строки.ПолучитьКлючи(); 
    // ... Выполнение запроса к регистру сведений ...
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        Для каждого Строчка Из МассивКлючей Цикл
            Строчка.СостояниеЗаявки = ВыборкаДетальныеЗаписи.СостояниеЗаявки;
            Строчка.ДатаСостояния = ВыборкаДетальныеЗаписи.ДатаСостояния;
        КонецЦикла; 
    КонецЦикла;
КонецПроцедуры

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

Важное правило объектной модели 1С: любые ссылки (СправочникСсылка, ДокументСсылка и т.д.) являются неизменяемыми объектами. Они служат лишь уникальными идентификаторами записей в базе данных. Вы не можете напрямую присвоить новое значение реквизиту ссылки (например, написать Ссылка.Реквизит = Значение). Для изменения данных в базе необходимо получить объект базы данных через метод ПолучитьОбъект(), изменить его свойства и вызвать метод Записать(). Но в контексте формы списка нам вовсе не нужно менять данные в базе — нам нужно лишь визуально отобразить их на форме.

Кроме того, обратите внимание на логическую ошибку в циклах. Конструкция вида:


Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    Для каждого Строчка Из МассивКлючей Цикл
        // присвоение...
    КонецЦикла;
КонецЦикла;

содержит вложенный цикл без какого-либо сопоставления данных. Это означает, что на каждом шаге обхода результатов запроса мы заново перезаписываем значения для всего массива строк. Это приводит к алгоритмической сложности $O(N \times M)$ и полностью ломает логику соответствия строк их реальным состояниям.

---

Причина №2: Дублирование ключевых значений в динамическом списке

По совету коллег автор темы попытался перевести форму списка на использование Динамического списка с произвольным запросом. Запрос выглядел следующим образом:


ВЫБРАТЬ
    ИТС_ВходящаяЗаявкаНаТОР.Ссылка КАК Ссылка,
    ...
    ИТС_ПодпискиТОРСрезПоследних.СостояниеЗаявки КАК СостояниеЗаявкиРегистр,
    ИТС_ПодпискиТОРСрезПоследних.ДатаСостояния КАК ДатаСостоянияРегистр
ИЗ
    РегистрСведений.ИТС_ПодпискиТОР.СрезПоследних КАК ИТС_ПодпискиТОРСрезПоследних
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ИТС_ВходящаяЗаявкаНаТОР КАК ИТС_ВходящаяЗаявкаНаТОР
        ПО ИТС_ПодпискиТОРСрезПоследних.Заявка = ИТС_ВходящаяЗаявкаНаТОР.Ссылка

Этот подход вызвал ошибку: «Обнаружено дублирование ключевых значений в колонках "Ссылка". Отображение данных в списке не возможно». Разберем по шагам, почему это произошло.

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

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

  1. Неправильное направление соединения. Ведущей таблицей запроса была выбрана виртуальная таблица СрезПоследних регистра сведений, к которой левым соединением подключалась таблица документов. При такой структуре, во-первых, документы, по которым вообще нет записей в регистре, просто исчезнут из списка. Во-вторых, если в регистре содержатся неактуальные или множественные записи, структура выборки искажается.
  2. Наличие дополнительных измерений в регистре. Виртуальная таблица СрезПоследних возвращает уникальные записи в разрезе всех измерений регистра. Если в регистре ИТС_ПодпискиТОР кроме измерения Заявка есть другие измерения (например, Услуга, Организация или Исполнитель), то для одной и той же заявки в срезе последних может находиться несколько записей (по одной для каждой комбинации измерений). При соединении с таблицей документов одна и та же ссылка на документ продублируется столько раз, сколько уникальных строк по этой заявке вернул срез последних.

---

Решение проблемы: Вариант А. Правильный запрос в Динамическом списке

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

Для этого мы можем использовать вложенный запрос с группировкой, который свернет данные регистра до уровня уникальных документов (заявок). Проанализируем следующий пример запроса:


ВЫБРАТЬ
    ДокументТаблица.Ссылка КАК Ссылка,
    ДокументТаблица.ПометкаУдаления КАК ПометкаУдаления,
    ДокументТаблица.Номер КАК Номер,
    ДокументТаблица.Дата КАК Дата,
    ДокументТаблица.Проведен КАК Проведен,
    // Выбираем другие необходимые реквизиты документа...
    ЕСТЬNULL(ПоследниеПодписки.СостояниеЗаявки, ЗНАЧЕНИЕ(Перечисление.СостоянияЗаявок.ПустаяСсылка)) КАК СостояниеЗаявкиРегистр,
    ЕСТЬNULL(ПоследниеПодписки.ДатаСостояния, ДАТАВРЕМЯ(1, 1, 1)) КАК ДатаСостоянияРегистр
ИЗ
    Документ.ИТС_ВходящаяЗаявкаНаТОР КАК ДокументТаблица
    
    // Подключаем регистр левым соединением, гарантируя отсутствие дублей
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            Срез.Заявка КАК Заявка,
            // Сворачиваем данные до одной строки на одну заявку с помощью агрегатных функций
            МАКСИМУМ(Срез.СостояниеЗаявки) КАК СостояниеЗаявки,
            МАКСИМУМ(Срез.ДатаСостояния) КАК ДатаСостояния
        ИЗ
            РегистрСведений.ИТС_ПодпискиТОР.СрезПоследних КАК Срез
        ГРУППИРОВАТЬ ПО
            Срез.Заявка
    ) КАК ПоследниеПодписки
    ПО ДокументТаблица.Ссылка = ПоследниеПодписки.Заявка

Преимущества данного подхода:

Недостатки: Использование вложенных запросов и группировок совместно с виртуальными таблицами среза последних в динамических списках может негативно сказаться на производительности при больших объемах данных (приводить к задержкам при прокрутке списка).

---

Решение проблемы: Вариант Б. Программное заполнение через обработчик «ПриПолученииДанныхНаСервере»

Если производительность списка критически важна, а сортировка и поиск по полям состояния не требуются, наилучшим решением будет использование серверного события динамического списка ПриПолученииДанныхНаСервере. Начиная с версии платформы 8.3.10, этот механизм работает чрезвычайно быстро и запрашивает данные из базы только для тех строк, которые физически отображаются на экране пользователя (около 20–45 строк).

Разберем по шагам, как реализовать этот алгоритм правильно.

Шаг 1. Создаем пустые колонки-заглушки в Динамическом списке. В запросе динамического списка мы просто объявляем пустые типизированные поля:


ВЫБРАТЬ
    Док.Ссылка КАК Ссылка,
    Док.Номер КАК Номер,
    Док.Дата КАК Дата,
    // Создаем пустые поля нужного типа
    ЗНАЧЕНИЕ(Перечисление.СостоянияЗаявок.ПустаяСсылка) КАК СостояниеЗаявкиРегистр,
    ДАТАВРЕМЯ(1,1,1) КАК ДатаСостоянияРегистр
ИЗ
    Документ.ИТС_ВходящаяЗаявкаНаТОР КАК Док

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


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    МассивКлючей = Строки.ПолучитьКлючи();
    Если МассивКлючей.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Запрос получает данные только для тех документов, которые отображены на экране
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	РегистрСрез.Заявка КАК Заявка,
        |	РегистрСрез.СостояниеЗаявки КАК СостояниеЗаявки,
        |	РегистрСрез.ДатаСостояния КАК ДатаСостояния
        |ИЗ
        |	РегистрСведений.ИТС_ПодпискиТОР.СрезПоследних(, Заявка В (&МассивКлючей)) КАК РегистрСрез";
    
    Запрос.УстановитьПараметр("МассивКлючей", МассивКлючей);
    ТаблицаДанных = Запрос.Выполнить().Выгрузить();
    
    // Заполняем Соответствие для быстрого поиска сопоставленных данных (сложность O(1))
    СоответствиеДанных = Новый Соответствие;
    Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
        СоответствиеДанных.Вставить(СтрокаТаблицы.Заявка, СтрокаТаблицы);
    КонецЦикла;
    
    // Заполняем визуальное представление строк на форме
    Для Каждого КлючСтроки Из МассивКлючей Цикл
        СтрокаСписка = Строки[КлючСтроки];
        ДанныеРегистра = СоответствиеДанных[КлючСтроки];
        
        Если ДанныеРегистра <> Неопределено Тогда
            // Изменяем данные отображения в специальном свойстве Данные строки списка
            СтрокаСписка.Данные.СостояниеЗаявкиРегистр = ДанныеРегистра.СостояниеЗаявки;
            СтрокаСписка.Данные.ДатаСостоянияRegистр   = ДанныеРегистра.ДатаСостояния;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Важное отличие: обратите внимание, что мы пишем значения не в сам КлючСтроки (который является ссылкой), а обращаемся к коллекции отображаемых строк через Строки[КлючСтроки] и меняем значения внутри структуры СтрокаСписка.Данные. Этот объект доступен для записи и предназначен именно для динамического дополнения отображаемых на форме данных.

---

Резюме и рекомендации

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

  1. Если по колонкам состояния пользователям требуется сортировка и отбор — используйте Вариант А (произвольный запрос динамического списка с левым соединением и обязательной группировкой для исключения дублирования строк).
  2. Если список содержит огромное количество записей, а дополнительные колонки нужны исключительно для чтения и визуального информирования — используйте Вариант Б (заполнение через ПриПолученииДанныхНаСервере с оптимизированным пакетным запросом и использованием Соответствия). Это обеспечит максимальное быстродействие интерфейса вашей конфигурации.
← На главную