Запись справочников, документов и регистров во встроенном языке 1С 8.3

· 9 мин

Содержание

Объект в 1С записывают методом Записать(): сначала получают сам объект (Ссылка.ПолучитьОбъект() или Справочники.Имя.СоздатьЭлемент()), меняют реквизиты и вызывают Объект.Записать() на сервере. У документа в метод передают режим: Записать(РежимЗаписиДокумента.Проведение). Ссылку записать нельзя — только объект.

Типичная ситуация: программист пишет обработку, которая должна проставить ответственного в 500 заказах. Код вроде бы работает, но на 37-м документе падает с ошибкой, и половина изменений уже в базе, а половина нет. Чтобы так не случалось, нужно понимать, что происходит при записи, где её оборачивать в транзакцию и как ловить исключения. Всё ниже относится к платформе 8.3 и управляемому приложению, отличия обычных форм вынесены отдельно.

Что происходит при записи объекта в 1С

Ссылка (СправочникСсылка, ДокументСсылка) — это только указатель на строку в базе. Её реквизиты можно прочитать, но изменить нельзя. Для изменения нужен объект (СправочникОбъект, ДокументОбъект), который живёт в памяти, пока вы его не запишете.

Когда вы вызываете Записать(), платформа делает следующее:

  1. 🔒 Открывает транзакцию, если её ещё нет. Каждая запись атомарна сама по себе.
  2. ⚙️ Вызывает обработчик ПередЗаписью(Отказ) в модуле объекта. Если там поставили Отказ = Истина, запись прерывается с исключением.
  3. 💾 Пишет данные в таблицы базы.
  4. ⚙️ Вызывает ПриЗаписи(Отказ). Здесь у нового объекта уже есть ссылка, а отказ откатывает всё, что было записано.
  5. ✅ Фиксирует транзакцию, если она была открыта самой записью.

Для документов перед ПередЗаписью и после ПриЗаписи добавляется проведение: обработчик ОбработкаПроведения(Отказ, РежимПроведения) и запись движений.

Записать() выполняется только на сервере. В клиентском коде управляемой формы объекта нет, есть только данные формы. Поэтому код записи выносят в процедуру с директивой &НаСервере или в общий серверный модуль.

Способы записать объект: справочник, документ, регистр

Метод называется одинаково, но у разных объектов свои параметры и своя логика. Сводная таблица:

Что записываемКак получить объектКак записать
Новый элемент справочникаСправочники.Имя.СоздатьЭлемент()Объект.Записать()
Новая группа справочникаСправочники.Имя.СоздатьГруппу()Объект.Записать()
Существующий элементСсылка.ПолучитьОбъект()Объект.Записать()
ДокументДокументы.Имя.СоздатьДокумент() или ПолучитьОбъект()Объект.Записать(РежимЗаписиДокумента.Запись)
Набор записей регистраРегистрыСведений.Имя.СоздатьНаборЗаписей()Набор.Записать(Истина)
Одна запись регистра сведенийРегистрыСведений.Имя.СоздатьМенеджерЗаписи()Менеджер.Записать()
Константа—Константы.Имя.Установить(Значение)

Справочник: создать и изменить элемент

&НаСервере
Процедура СоздатьКонтрагентаНаСервере()

	НовыйЭлемент = Справочники.Контрагенты.СоздатьЭлемент();
	НовыйЭлемент.Наименование = "ООО Ромашка";
	НовыйЭлемент.ИНН = "7701234567";
	НовыйЭлемент.Записать();

	Сообщить("Создан элемент с кодом " + НовыйЭлемент.Код);

КонецПроцедуры

Код и номер платформа присваивает сама при первой записи, если в свойствах объекта включена автонумерация. Чтобы изменить существующий элемент, получите объект по ссылке:

Объект = СсылкаНаКонтрагента.ПолучитьОбъект();
Объект.Комментарий = "Проверен";
Объект.Записать();

Если ссылка пустая или объект уже удалён, ПолучитьОбъект() вернёт Неопределено. Проверяйте это до обращения к реквизитам.

Документ: запись, проведение и отмена проведения

У документа три режима записи — значения системного перечисления РежимЗаписиДокумента:

  • 📝 Запись — сохранить реквизиты документа. Обработчик проведения не вызывается, новые движения не формируются.
  • ✅ Проведение — записать и провести, сформировать движения.
  • ↩️ ОтменаПроведения — записать и удалить движения.
Документ = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
Документ.Дата = ТекущаяДатаСеанса();
// ... заполнение реквизитов и табличной части
Документ.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);

Второй параметр — РежимПроведенияДокумента.Оперативный или Неоперативный. Оперативное проведение доступно, только если оно разрешено в свойствах документа и документ проводится текущим днём.

Регистр сведений и накопления

Набор записей пишется целиком. Сначала ставится отбор, затем набор заполняется и записывается с замещением:

Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
Набор.Отбор.Номенклатура.Установить(Товар);
Набор.Отбор.Период.Установить(НачалоДня(ТекущаяДатаСеанса()));

Запись = Набор.Добавить();
Запись.Период = НачалоДня(ТекущаяДатаСеанса());
Запись.Номенклатура = Товар;
Запись.Цена = 1250;

Набор.Записать(Истина); // Истина — заменить записи по отбору

Если вызвать Записать(Истина) без отбора, набор заменит все записи регистра. Это частая причина потери данных в регистрах сведений.

Движения документа по регистрам накопления пишут внутри проведения через коллекцию Движения: Движения.ТоварыНаСкладах.Записывать = Истина, затем добавляют строки. Платформа сама запишет наборы после ОбработкаПроведения.

Транзакционная запись нескольких объектов

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

Каноническая схема для 8.3:

НачатьТранзакцию();
Попытка

	Блокировка = Новый БлокировкаДанных;
	Элемент = Блокировка.Добавить("Справочник.Номенклатура");
	Элемент.УстановитьЗначение("Ссылка", Товар);
	Блокировка.Заблокировать();

	Объект = Товар.ПолучитьОбъект();
	Объект.Артикул = НовыйАртикул;
	Объект.Записать();

	// ... другие записи, которые должны пройти вместе

	ЗафиксироватьТранзакцию();

Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

Что здесь главное:

  • 🔐 Управляемая блокировка ставится до чтения объекта. Так два сеанса не перезапишут друг друга.
  • ↩️ В блоке Исключение первым делом вызывается ОтменитьТранзакцию(), а уже потом всё остальное.
  • 📢 ВызватьИсключение без параметров пробрасывает ошибку выше. Вызывающий код узнает о сбое, а не получит «тихий» откат.

Если внутри транзакции перехватить исключение и продолжить работу, транзакция остаётся «сломанной». Любая следующая запись упадёт с ошибкой В данной транзакции уже происходили ошибки!. Ловить ошибки внутри транзакции и идти дальше нельзя.

Транзакции вкладываются, но вложенная не самостоятельна. ЗафиксироватьТранзакцию() во вложенном вызове ничего не фиксирует, реальная фиксация происходит на внешнем уровне. Отмена на любом уровне откатывает всё.

Как перехватить ошибку записи

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

Попытка
	Объект.Записать();
Исключение
	ИнфоОбОшибке = ИнформацияОбОшибке();
	ЗаписьЖурналаРегистрации("Запись объекта",
		УровеньЖурналаРегистрации.Ошибка,
		Объект.Метаданные(),
		Объект.Ссылка,
		ПодробноеПредставлениеОшибки(ИнфоОбОшибке));
	Сообщить("Не удалось записать: " + КраткоеПредставлениеОшибки(ИнфоОбОшибке));
КонецПопытки;

Пользователю показывают краткое представление, в журнал регистрации пишут подробное — со стеком вызовов. Так проще потом найти, в каком модуле сработал отказ.

Как правильно отказать в записи

В модуле объекта отказ ставят в обработчике и сразу сообщают причину:

Процедура ПередЗаписью(Отказ)

	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	Если ПустаяСтрока(ИНН) Тогда
		ОбщегоНазначения.СообщитьПользователю("Не заполнен ИНН", ЭтотОбъект, "ИНН", , Отказ);
	КонецЕсли;

КонецПроцедуры

ОбщегоНазначения.СообщитьПользователю — процедура Библиотеки стандартных подсистем, она есть в типовых конфигурациях. В пустой конфигурации без БСП используйте объект СообщениеПользователю и установку Отказ = Истина.

Проверку заполнения через ОбработкаПроверкиЗаполнения платформа вызывает при интерактивной записи из формы. При записи из кода она не срабатывает. Если проверка нужна, вызовите Объект.ПроверитьЗаполнение() до Записать() и прервите запись, если метод вернул Ложь. У документов проверка заполнения по умолчанию выполняется только при проведении.

Запись из управляемой и обычной формы

В управляемой форме реквизит Объект — это не объект базы, а его данные в виде структуры для клиента. Поэтому привычное Объект.Записать() на клиенте не сработает.

Управляемая форма: три рабочих варианта

  1. Стандартная запись формы. На клиенте вызовите метод формы Записать(). Он возвращает Истина при успехе и запускает все обработчики формы и модуля объекта. Это предпочтительный путь из кода формы.
  2. Обработчики событий формы. Если нужно что-то поменять в объекте прямо перед записью, используйте ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи). Параметр ТекущийОбъект — это уже настоящий объект базы.
  3. Ручное преобразование. На сервере РеквизитФормыВЗначение("Объект") даёт объект, после записи данные возвращают в форму через ЗначениеВРеквизитФормы(ОбъектБазы, "Объект").
&НаСервере
Процедура ЗаполнитьИЗаписатьНаСервере()

	ОбъектБазы = РеквизитФормыВЗначение("Объект");
	ОбъектБазы.Комментарий = "Заполнено автоматически";
	ОбъектБазы.Записать();
	ЗначениеВРеквизитФормы(ОбъектБазы, "Объект");

КонецПроцедуры

Порядок событий при интерактивной записи такой: ПередЗаписью на клиенте → ПередЗаписьюНаСервере → обработчики модуля объекта → ПриЗаписиНаСервере → ПослеЗаписиНаСервере → ПослеЗаписи на клиенте. Код, который должен видеть ссылку нового объекта, ставьте в ПриЗаписиНаСервере или позже.

Третий вариант обходит форму. Если открытую форму параллельно редактирует пользователь, его несохранённые правки могут затереться при возврате данных. Для открытой формы лучше первые два варианта.

Обычная форма

В толстом клиенте форма работает прямо с объектом. В модуле формы объект доступен через основной реквизит, и для записи из кода формы используют метод ЗаписатьВФорме(). Он выполняет запись так же, как кнопка «Записать», с вызовом обработчиков формы. Прямой Записать() у основного реквизита тоже работает, но форма не узнает о записи и не обновит своё состояние.

Массовая запись объектов без тормозов

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

Что помогает:

  • 📦 Порции. Не пишите 50 000 документов в одной транзакции: она держит блокировки и раздувает журнал СУБД. Разбейте на порции по 100–1000 объектов, каждая — своя транзакция.
  • 🔍 Выборка запросом. Получите список ссылок одним запросом, а не обходом справочника через Выбрать() с проверкой каждого элемента.
  • ⏭️ ОбменДанными.Загрузка = Истина. Ставится перед Записать(). Обработчики ПередЗаписью и ПриЗаписи всё равно вызываются, но типовой код в них сразу выходит по проверке этого флага. Проверки заполнения и бизнес-логика пропускаются.
  • 🧰 Механизмы БСП. В обработчиках обновления типовых конфигураций используют ОбновлениеИнформационнойБазы.ЗаписатьОбъект(Объект) — он пишет без бизнес-логики и с учётом обмена.
  • 🖥️ Фоновое задание. Долгую обработку запускайте в фоне, чтобы не держать клиент и не упираться в таймаут.

Режим ОбменДанными.Загрузка отключает проверки. Включайте его только тогда, когда сами отвечаете за корректность данных: например, при переносе из другой базы или при исправлении служебного реквизита.

Пример порционной записи:

РазмерПорции = 500;
Счетчик = 0;
НачатьТранзакцию();
Попытка
	Для Каждого Ссылка Из СписокСсылок Цикл
		Объект = Ссылка.ПолучитьОбъект();
		Объект.Ответственный = НовыйОтветственный;
		Объект.ОбменДанными.Загрузка = Истина;
		Объект.Записать();
		Счетчик = Счетчик + 1;
		Если Счетчик % РазмерПорции = 0 Тогда
			ЗафиксироватьТранзакцию();
			НачатьТранзакцию();
		КонецЕсли;
	КонецЦикла;
	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

При сбое откатится только текущая порция, уже зафиксированные останутся в базе. Поэтому обработку делайте повторяемой: при повторном запуске она должна пропускать объекты, где новое значение уже стоит.

Документы при массовом перепроведении пишите в порядке даты и времени. Иначе остатки по регистрам будут считаться на промежуточном, неверном состоянии.

Ошибки, на которых спотыкаются при записи объектов

Диагностика: выберите симптом

Ещё три ошибки, которые не дают явного сообщения, но портят данные:

  1. Запись в цикле обхода самого справочника. Если вы перебираете Выбрать() и тут же пишете элементы, выборка может повторно вернуть изменённые записи. Сначала соберите ссылки в массив, потом меняйте.
  2. Изменение объекта в ПриЗаписи. Присваивание реквизиту в ПриЗаписи не попадает в базу: данные уже записаны. Меняйте реквизиты в ПередЗаписью.
  3. Повторный Записать() внутри своего же обработчика. Это рекурсия, которая заканчивается переполнением стека или зацикливанием. Если нужно передать признак между обработчиками, используйте ДополнительныеСвойства объекта.

Частые вопросы о записи объектов

Как узнать, новый ли объект перед записью?
Вызовите Объект.ЭтоНовый(). Метод возвращает Истина, пока объект ни разу не записан. В ПриЗаписи он уже вернёт Ложь, поэтому признак сохраняют в ДополнительныеСвойства ещё в ПередЗаписью.
Можно ли заранее получить ссылку нового объекта?
Да. Получите ссылку методом менеджера ПолучитьСсылку() и назначьте её объекту через Объект.УстановитьСсылкуНового(Ссылка) до первой записи. Это нужно, когда на объект ссылаются из других записей той же транзакции.
Чем ПометкаУдаления отличается от Удалить()?
Объект.УстановитьПометкуУдаления(Истина) ставит пометку, а объект физически удаляется позже через удаление помеченных с контролем ссылок. Объект.Удалить() удаляет сразу и без проверки ссылочной целостности, им пользуются осторожно.
Нужны ли права, если код выполняется на сервере?
Да, по умолчанию запись проверяет права текущего пользователя. Привилегированный режим УстановитьПривилегированныйРежим(Истина) снимает проверку, но применять его стоит только в служебном коде, где вы контролируете, что пишется.
Почему Записать() у документа не формирует движения?
Вызван режим РежимЗаписиДокумента.Запись или в ОбработкаПроведения не установлено Записывать = Истина для наборов движений. Для проведения передайте РежимЗаписиДокумента.Проведение.