Как обновить данные в форме документа после их изменения из внешней обработки и избежать ошибки "Данные были изменены"?

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

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

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

Анализ исходной ситуации и причины ошибки

Проанализируем типичный сценарий, который приводит к ошибке. У нас есть внешняя обработка, которая вызывается из формы документа (например, «Поступление товаров и услуг»). Код обработки выглядит примерно так:

Клиентская процедура, которая запускает процесс:


&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, МассивДокументов) Экспорт
	Перем ВыбЗнач;
	СсылкаНаДокумент = МассивДокументов[0];
	
	Массив = Новый Массив;
	Массив.Добавить(Тип("ПланСчетовСсылка.Хозрасчетный"));
	ОписаниеТипов = Новый ОписаниеТипов(Массив);
	
	Если ВвестиЗначение(ВыбЗнач, "Выберете счет", ОписаниеТипов) Тогда
		ТекСчет = ВыбЗнач;
		ЗаполнитьПоСчетуНаСервере(СсылкаНаДокумент, ТекСчет);
	КонецЕсли;
КонецПроцедуры

Серверная процедура, которая выполняет основную работу по изменению документа:


&НаСервере
Процедура ЗаполнитьПоСчетуНаСервере(Документ, ТекСчет) Экспорт
	ДокОбъект = Документ.ПолучитьОбъект();
	
	Если ТипЗнч(Документ) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
		// ...здесь выполняется запрос к данным
		// и присваивание новых значений реквизитам и табличным частям...
		// Например: ДокОбъект.Товары.Загрузить(РезультатЗапроса);
	КонецЕсли;
	
	ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры

Выясним причину проблемы. Весь процесс можно разбить на шаги:

  1. Пользователь открывает форму документа. В этот момент данные из базы считываются в основной реквизит формы (обычно Объект) и получают определенную версию.
  2. Из этой формы пользователь запускает нашу внешнюю обработку.
  3. Обработка на сервере с помощью метода Документ.ПолучитьОбъект() снова считывает данные документа из базы данных.
  4. Код изменяет полученный объект и записывает его обратно в базу методом ДокОбъект.Записать(). В этот момент в базе данных сохраняется новая версия объекта.
  5. Обработка завершает работу, управление возвращается в открытую форму документа. Но данные в форме (в реквизите Объект) остались прежними, со старой версией. Визуально пользователь не видит никаких изменений.
  6. Пользователь нажимает кнопку «Записать» или «Провести». Платформа пытается записать данные из формы, но перед этим сверяет версию объекта в форме с версией в базе данных. Обнаружив, что в базе уже лежит более новая версия (записанная нашей обработкой), платформа прерывает операцию и выдает ошибку о конфликте версий.

Чтобы решить эту проблему, нам необходимо после программной записи документа заставить открытую форму перечитать свои данные из базы данных. Рассмотрим два основных способа это сделать.

Решение 1: Стандартный механизм оповещений (рекомендуемый)

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

Шаг 1: Отправка оповещения из внешней обработки

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

Модифицируем клиентскую процедуру обработки:


&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, МассивДокументов) Экспорт
	// ... код выбора счета ...
	СсылкаНаДокумент = МассивДокументов[0];
	
	Если ВвестиЗначение(ВыбЗнач, "Выберете счет", ОписаниеТипов) Тогда
		ТекСчет = ВыбЗнач;
		ЗаполнитьПоСчетуНаСервере(СсылкаНаДокумент, ТекСчет);
		
		// Сообщаем всем формам, что документ был изменен
		ОповеститьОбИзменении(СсылкаНаДокумент);
	КонецЕсли;
КонецПроцедуры

Шаг 2: Обработка оповещения в форме документа

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

В модуле формы документа, который вы хотите обновлять, добавьте следующий код:


&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
	Если Параметр = Объект.Ссылка Тогда
		// Обновляем данные формы из базы данных
		Прочитать(); 
		// Можно также использовать более полный вариант обновления
		// ОбновитьОтображениеДанных();
	КонецЕсли;
КонецПроцедуры

Этот метод будет вызван во всех открытых формах, и если параметр оповещения (ссылка на измененный документ) совпадет со ссылкой объекта текущей формы, форма обновит свои данные.

Решение 2: Принудительное обновление формы извне (без изменения конфигурации)

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

Шаг 1: Получение коллекции открытых окон

На клиенте можно получить массив всех открытых окон приложения с помощью функции ПолучитьОкна().

Шаг 2: Поиск нужной формы и ее обновление

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

Вот готовый код, который нужно вставить в клиентскую процедуру ВыполнитьКоманду после вызова серверного метода:


&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, МассивДокументов) Экспорт
	// ... ваш код по вызову серверной процедуры ЗаполнитьПоСчетуНаСервере ...
	ЗаполнитьПоСчетуНаСервере(СсылкаНаДокумент, ТекСчет);

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

Важные моменты этого решения:

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

← На главную