В практике разработки на платформе 1С:Предприятие часто возникает задача предоставить пользователю удобный интерфейс «в один клик». Например, установить флажок оплаты прямо в табличной части на форме, не открывая при этом тяжелые документы. Однако, если данные хранятся в подчиненном регистратору регистре сведений, стандартное интерактивное редактирование становится неочевидным. Платформа жестко контролирует запись в такие регистры, связывая их с проведением документов.
В этой статье мы подробно разберем, как обойти эти ограничения, реализуем удобный интерфейс на управляемой форме и рассмотрим несколько программных механизмов записи данных обратно в базу — от классических до современных высокопроизводительных решений, появившихся в платформе версии 8.3.26.
Прежде чем перейти к коду, выясним причину сложности. Подчиненный регистр сведений (где свойство «Режим записи» установлено в значение «Подчинение регистратору») хранит записи, которые жестко привязаны к конкретному документу. В стандартной логике 1С эти записи создаются в момент проведения документа в процедуре ОбработкаПроведения.
Если мы просто поменяем значение в таблице значений на форме, платформа сама по себе не запишет изменения в базу данных. Нам необходимо программно перенести эти данные в регистр. Рассмотрим четыре основных способа реализации этой задачи.
Этот способ интуитивно понятен, но наиболее ресурсоемок. Его суть заключается в том, что мы открываем документ-регистратор на сервере, меняем его реквизиты или табличную часть и заново проводим его.
Разберем алгоритм по шагам:
Записать(РежимЗаписиДокумента.Проведение).Посмотрим на пример кода, реализующий этот подход:
&НаСервереБезКонтекста
Процедура ИзменитьДанныеЧерезПроведение(СсылкаНаРегистратор, Договор, НовоеЗначениеОплаты)
// Получаем объект документа для редактирования
ДокументОбъект = СсылкаНаРегистратор.ПолучитьОбъект();
// Находим нужную строку в табличной части документа (например, по договору)
ПараметрыПоиска = Новый Структура("Договор", Договор);
НайденныеСтроки = ДокументОбъект.ЭтапыОплаты.НайтиСтроки(ПараметрыПоиска);
Для Каждого СтрокаДокумента Из НайденныеСтроки Цикл
СтрокаДокумента.Оплачено = НовоеЗначениеОплаты;
КонецЦикла;
// Перепроводим документ, чтобы обновились движения в регистре
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ВызватьИсключение "Не удалось перепровести документ: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Недостатки метода: Полное перепроведение документа запускает все механизмы проверок, остатков, взаиморасчетов и проведение по всем остальным регистрам (накопления, бухгалтерии и т.д.). Это может работать крайне медленно и приводить к блокировкам базы данных при одновременной работе пользователей.
Платформа 1С позволяет напрямую работать с записями регистра по конкретному документу с помощью объекта РегистрСведенийНаборЗаписей. Это значительно быстрее, так как не затрагивает сам документ и другие его движения.
Проанализируем ситуацию: нам нужно изменить лишь один ресурс в одной записи регистра по конкретному регистратору. Для этого мы установим отбор по регистратору, прочитаем набор, найдем нужную запись, отредактируем ее и запишем обратно.
&НаСервереБезКонтекста
Процедура ИзменитьДвиженияРегистраНапрямую(СсылкаНаРегистратор, Договор, НовоеЗначениеОплаты)
// Создаем набор записей конкретного регистра сведений
НаборЗаписей = РегистрыСведений.СведенияОбОплатеДоговоров.СоздатьНаборЗаписей();
// Устанавливаем жесткий отбор по нашему регистратору
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаРегистратор);
// Читаем существующие движения из базы данных
НаборЗаписей.Прочитать();
ИзмененияВнесены = Ложь;
// Ищем запись по ключевым измерениям (в данном случае - Договор)
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Договор = Договор Тогда
Если Запись.Оплачено <> НовоеЗначениеОплаты Тогда
Запись.Оплачено = НовоеЗначениеОплаты;
ИзмененияВнесены = Истина;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Записываем набор обратно в базу только при наличии реальных изменений
Если ИзмененияВнесены Тогда
НаборЗаписей.Записать(Истина); // Параметр Истина означает замещение старых записей набора
КонецЕсли;
КонецПроцедуры
Преимущества метода: Работает мгновенно, не вызывает тяжелых обработчиков проведения документа.
При использовании Способа 2 возникает серьезная проблема: если кто-то из пользователей в будущем решит перепровести документ-регистратор стандартным образом, то логика в ОбработкаПроведения заново сформирует движения. В результате ручная отметка об оплате, которую мы записали напрямую, будет стерта.
Чтобы решить эту проблему, применим паттерн «Ручная корректировка», используемый в типовых конфигурациях 1С:
РучнаяКорректировка с типом Булево.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Если включена ручная корректировка, пропускаем формирование движений по этому регистру
Если ЭтотОбъект.РучнаяКорректировка Тогда
Возврат;
КонецЕсли;
// Стандартный алгоритм проведения...
// Движения.СведенияОбОплатеДоговоров.Записать();
КонецПроцедуры
Теперь доработаем нашу серверную процедуру сохранения изменений. При изменении ячейки на форме мы будем не только менять движения в регистре, но и программно взводить флаг ручной корректировки в самом документе (просто сохраняя его объект без проведения):
&НаСервереБезКонтекста
Процедура СохранитьСУчетомРучнойКорректировки(СсылкаНаРегистратор, Договор, НовоеЗначениеОплаты)
// 1. Установим флаг ручной корректировки в документе
Если Не СсылкаНаРегистратор.РучнаяКорректировка Тогда
ДокументОбъект = СсылкаНаРегистратор.ПолучитьОбъект();
ДокументОбъект.РучнаяКорректировка = Истина;
// Записываем документ БЕЗ проведения, чтобы зафиксировать признак корректировки
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
// 2. Записываем изменения напрямую в регистр сведений
НаборЗаписей = РегистрыСведений.СведенияОбОплатеДоговоров.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаРегистратор);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Договор = Договор Тогда
Запись.Оплачено = НовоеЗначениеОплаты;
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать(Истина);
КонецПроцедуры
Начиная с версии платформы 8.3.26, в методе Записать() набора записей появился новый параметр, принимающий значения системного перечисления РежимЗамещения. Ранее при перезаписи набора платформа всегда выполняла физическое удаление всех строк по регистратору в СУБД, а затем вставляла их заново.
Теперь мы можем использовать РежимЗамещения.Обновление. Этот режим позволяет обновить только те поля, которые реально изменились, сопоставляя записи по измерениям (в нашем случае по измерению Договор). Это исключает избыточную нагрузку на СУБД.
&НаСервереБезКонтекста
Процедура ОбновитьЗаписьСовременнымМетодом(СсылкаНаРегистратор, Договор, НовоеЗначениеОплаты)
НаборЗаписей = РегистрыСведений.СведенияОбОплатеДоговоров.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаРегистратор);
// Создаем новую запись, которую нужно обновить
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Регистратор = СсылкаНаРегистратор;
НоваяЗапись.Договор = Договор;
НоваяЗапись.Оплачено = НовоеЗначениеОплаты;
// Используем новый режим замещения (доступен с платформы 8.3.26)
// Обновляются только ресурсы указанной записи, старые записи по другим договорам не пострадают
НаборЗаписей.Записать(РежимЗамещения.Обновление);
КонецПроцедуры
Использование этого режима существенно повышает производительность системы, снижая нагрузку на транзакционный лог базы данных СУБД.
Теперь объединим серверную логику с интерактивными действиями пользователя на клиенте. Разберем настройку элементов управляемой формы по шагам.
Допустим, на форме создана Таблица Значений с именем реквизита ДоговорыКОплате. В этой таблице присутствуют следующие колонки:
Договор (Тип: СправочникСсылка.ДоговорыКонтрагентов)Регистратор (Тип: ДокументСсылка.ПлатежноеПоручение или другой определяемый тип)Оплачено (Тип: Булево)Добавляем таблицу ДоговорыКОплате на форму в виде элемента «Таблица». Для колонки Оплачено устанавливаем свойство «ПутьКДанным» в значение ДоговорыКОплате.Оплачено, а свойство «Вид» — в значение Поле флажка.
Для элемента формы (поля флажка колонки «Оплачено») создаем событие ПриИзменении. В обработчике этого события мы получим данные текущей строки и вызовем серверную процедуру для записи изменений.
&НаКлиенте
Процедура ДоговорыКОплатеОплаченоПриИзменении(Элемент)
// Получаем строку, которую редактирует пользователь в данный момент
ТекущаяСтрока = Элементы.ДоговорыКОплате.ТекущиеДанные;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
// Передаем управление на сервер без контекста для быстрой отработки транзакции
СохранитьИзменениеЯчейкиНаСервере(ТекущаяСтрока.Регистратор, ТекущаяСтрока.Договор, ТекущаяСтрока.Оплачено);
ПоказатьОповещениеПользователя("Сохранено", , "Статус оплаты договора обновлен в базе данных.", БиблиотекаКартинок.Информация);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СохранитьИзменениеЯчейкиНаСервере(СсылкаНаРегистратор, Договор, НовоеЗначениеОплаты)
// Реализуем запись выбранным методом (например, Способом 2 с прямой перезаписью)
НаборЗаписей = РегистрыСведений.СведенияОбОплатеДоговоров.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаРегистратор);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Договор = Договор Тогда
Запись.Оплачено = НовоеЗначениеОплаты;
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать(Истина);
КонецПроцедуры
Важная деталь оптимизации: Использование директивы компиляции &НаСервереБезКонтекста критически важно. Это позволяет избежать передачи всей структуры управляемой формы на сервер и обратно, что минимизирует интернет-трафик между клиентом и сервером и ускоряет реакцию интерфейса до долей секунды.
Мы подробно разобрали задачу редактирования ячейки таблицы управляемой формы со связью с подчиненным регистром сведений. При выборе метода реализации руководствуйтесь следующими правилами:
РежимЗамещения.Обновление, так как он максимально экономит ресурсы сервера базы данных.ПриИзменении и тонкий серверный вызов &НаСервереБезКонтекста, передавая только ключевые параметры строки, а не всю форму целиком.