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