Документ в коде 1С 8.3: от поиска ссылки до проведения и удаления

· 10 мин

Содержание

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

Всё, что меняет данные, выполняется на сервере. На тонком клиенте и в веб-клиенте объект документа недоступен: из модуля формы обращаются к серверной процедуре и передают туда ссылку. Ниже — рабочие фрагменты на встроенном языке для каждой операции, на примере документа РеализацияТоваровУслуг. Имя замените на своё из ветки Документы дерева конфигурации.

Ссылка и объект: что выбрать для задачи

Перед тем как писать код, решите, что именно нужно сделать с документом. От этого зависит, какой тип значения брать.

ТипКак получитьДля чегоСтоимость
ДокументСсылкаНайтиПоНомеру(), ПолучитьСсылку(), запрос, реквизит другого объектапередать в параметр, сравнить, прочитать 1-2 полядёшево, пока не читаете поля
ДокументОбъектСсылка.ПолучитьОбъект(), Документы.Имя.СоздатьДокумент()изменить, записать, провести, пометить на удалениечитает документ целиком со всеми табличными частями
ДанныеФормыСтруктура (Объект в форме)автоматически в форме документаработа в открытой формеживёт только в форме
Выборка запросаЗапрос.Выполнить().Выбрать()массовое чтение полейсамый быстрый способ читать

💡 Простое правило: читать — запросом или через ссылку, менять — через объект. Объект ради одного поля «Дата» поднимать не нужно.

Как найти документ: по номеру, по GUID и запросом

По номеру через менеджер

Метод НайтиПоНомеру(Номер, ДатаИнтервала) ищет документ по номеру. Второй параметр нужен, когда номер уникален только в пределах периода (год, квартал, месяц — задаётся свойством «Периодичность» документа в конфигураторе). Без даты при периодической нумерации вернётся случайный документ из любого года с таким номером.

Номер = "0000-000123";
ДатаПериода = Дата(2025, 3, 1);

Ссылка = Документы.РеализацияТоваровУслуг.НайтиПоНомеру(Номер, ДатаПериода);

Если Ссылка.Пустая() Тогда
	Сообщить("Документ с номером " + Номер + " не найден");
Иначе
	Сообщить("Найден документ от " + Формат(Ссылка.Дата, "ДФ=dd.MM.yyyy"));
КонецЕсли;

Номер передавайте ровно так, как он хранится в базе, с префиксом и ведущими нулями. В печатных формах типовых конфигураций номер выводится без префикса и нулей («123»), а в базе это строка «0000-000123». Если ищете по «123», ничего не найдётся.

Если документ не найден, метод возвращает пустую ссылку, а не Неопределено. Проверяйте результат через Ссылка.Пустая().

По уникальному идентификатору

GUID документа не меняется при перенумерации и одинаков в узлах обмена, поэтому именно его стоит хранить во внешних системах и логах.

// Получить GUID
ИдентификаторСтрокой = Строка(Ссылка.УникальныйИдентификатор());

// Восстановить ссылку по GUID
Идентификатор = Новый УникальныйИдентификатор(ИдентификаторСтрокой);
Ссылка = Документы.РеализацияТоваровУслуг.ПолучитьСсылку(Идентификатор);

⚠️ ПолучитьСсылку() не проверяет, есть ли такой документ в базе. Вернётся ссылка, но документа за ней может не быть — «битая ссылка». Как это проверить, показано в следующем разделе.

Запросом — когда условие сложнее номера

Запрос нужен, если ищете по контрагенту, сумме, периоду или сразу несколько документов.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
|	Док.Ссылка КАК Ссылка
|ИЗ
|	Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
|	Док.Номер = &Номер
|	И Док.Дата МЕЖДУ &Начало И &Конец
|	И НЕ Док.ПометкаУдаления";

Запрос.УстановитьПараметр("Номер", "0000-000123");
Запрос.УстановитьПараметр("Начало", НачалоГода(Дата(2025, 1, 1)));
Запрос.УстановитьПараметр("Конец", КонецГода(Дата(2025, 1, 1)));

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
	Ссылка = Выборка.Ссылка;
КонецЕсли;

Когда имя документа известно только во время работы

Менеджер можно получить по строке: Документы["РеализацияТоваровУслуг"]. Так пишут универсальные обработки. Имя вида документа по ссылке возвращает Ссылка.Метаданные().Имя.

Создание нового документа программно

Новый документ создаёт метод менеджера СоздатьДокумент(). До вызова Записать() документ существует только в памяти, в базе его нет.

Док = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
Док.Дата = ТекущаяДатаСеанса();
Док.Организация = Организация;
Док.Контрагент = Контрагент;
Док.Комментарий = "Создан обработкой загрузки";

// Табличная часть
НоваяСтрока = Док.Товары.Добавить();
НоваяСтрока.Номенклатура = Номенклатура;
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 150;
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;

Док.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Создан документ № " + Док.Номер);

Что учесть при создании:

  • 🔢 Номер. При автонумерации платформа присвоит номер при первой записи, если поле пустое. Получить номер заранее можно методом Док.УстановитьНовыйНомер().
  • 📅 Дата. Берите ТекущаяДатаСеанса(), а не ТекущаяДата(): первая учитывает часовой пояс сеанса, вторая возвращает время компьютера, на котором выполняется код.
  • 🧮 Расчётные поля. Программное заполнение не вызывает обработчики формы. Суммы, НДС, ставки, которые в форме считаются при изменении количества, в коде нужно рассчитать самому или вызвать процедуры модуля менеджера, если конфигурация их предоставляет.
  • 🧩 Заполнение по умолчанию. Метод Док.Заполнить(ДанныеЗаполнения) вызывает обработчик ОбработкаЗаполнения модуля объекта. В типовых конфигурациях там подставляются организация, склад, ответственный и прочие значения по умолчанию.

Копирование существующего документа

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

Исходный = Ссылка.ПолучитьОбъект();
Копия = Исходный.Скопировать();
Копия.Дата = ТекущаяДатаСеанса();
Копия.Номер = "";
Копия.Записать(РежимЗаписиДокумента.Запись);

Чтение реквизитов и табличных частей документа

Через ссылку

Обращение Ссылка.Контрагент работает, но платформа при этом читает из базы все реквизиты шапки документа и кеширует их. Ради одного поля в цикле по тысяче документов это тысяча лишних чтений.

// Нормально для одного документа
Контрагент = Ссылка.Контрагент;
Сумма = Ссылка.СуммаДокумента;

Через функции БСП

В конфигурациях на Библиотеке стандартных подсистем (БП 3.0, ЗУП 3.1, УТ 11, ERP) есть функции, которые читают нужные поля одним запросом:

// Одно поле
Контрагент = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Контрагент");

// Несколько полей — возвращается структура
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "Дата, Номер, Контрагент");
Сообщить(Реквизиты.Номер + " от " + Формат(Реквизиты.Дата, "ДЛФ=D"));

Табличная часть

Табличную часть читают через объект или запросом к таблице Документ.Имя.ИмяТабличнойЧасти.

Объект = Ссылка.ПолучитьОбъект();
Для Каждого Строка Из Объект.Товары Цикл
	Сообщить(Строка(Строка.Номенклатура) + ": " + Формат(Строка.Количество, "ЧГ=0"));
КонецЦикла;

Вариант запросом, когда нужен только итог:

Запрос = Новый Запрос(
"ВЫБРАТЬ
|	СУММА(Т.Количество) КАК Количество
|ИЗ
|	Документ.РеализацияТоваровУслуг.Товары КАК Т
|ГДЕ
|	Т.Ссылка = &Ссылка");
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
Итого = Выборка.Количество;

Как изменить документ и записать его в базу

Изменение всегда идёт по схеме «ссылка → объект → правка → запись». Перед этим проверьте, что документ вообще существует: для битой ссылки ПолучитьОбъект() в 8.3 возвращает Неопределено.

Объект = Ссылка.ПолучитьОбъект();
Если Объект = Неопределено Тогда
	ВызватьИсключение "Документ не найден в базе";
КонецЕсли;

Объект.Заблокировать(); // объектная блокировка от правки другим пользователем
Объект.Комментарий = "Исправлено " + Формат(ТекущаяДатаСеанса(), "ДЛФ=D");

Попытка
	Объект.Записать(РежимЗаписиДокумента.Запись);
Исключение
	Сообщить("Не удалось записать: " + ОписаниеОшибки());
КонецПопытки;

Режимы записи

РежимЧто делает с движениями
РежимЗаписиДокумента.Записьсохраняет реквизиты; у проведённого документа движения остаются старыми
РежимЗаписиДокумента.Проведениесохраняет и перепроводит, движения формируются заново
РежимЗаписиДокумента.ОтменаПроведениясохраняет и удаляет все движения

Если вы изменили сумму или количество в проведённом документе и записали его в режиме Запись, остатки и проводки останутся по старым данным. Для таких правок используйте Проведение.

Запись без обработчиков: ОбменДанными.Загрузка

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

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

Проведение и отмена проведения из кода

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

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

Если в обработчике проведения установлен Отказ = Истина (не хватает остатков, не заполнен склад, закрыт период), метод Записать() вызывает исключение. Без Попытка … Исключение обработка остановится на первом проблемном документе.

Оперативный или неоперативный

Второй параметр метода — режим проведения:

  • ⏱ Оперативный — только для документов текущей датой, если в конфигураторе у документа разрешено оперативное проведение. Платформа ставит документу текущее время.
  • 📆 Неоперативный — для документов прошлыми датами и для массового перепроведения. Его и используйте по умолчанию в обработках.

Если параметр не указан, используется неоперативный режим.

Отмена проведения

Объект.Записать(РежимЗаписиДокумента.ОтменаПроведения);

Проверить состояние можно свойством Ссылка.Проведен или Объект.Проведен. Присвоить ему значение нельзя — оно только для чтения и меняется режимом записи.

Массовое перепроведение лучше выполнять обработкой по датам по возрастанию: документы с более поздней датой зависят от остатков, которые формируют ранние.

Пометка на удаление и удаление документов

Пометка на удаление

Объект = Ссылка.ПолучитьОбъект();
Объект.УстановитьПометкуУдаления(Истина);

При пометке проведённого документа платформа отменяет его проведение: движения удаляются. Снять пометку можно тем же методом с параметром Ложь, но проведение вернётся только после повторного проведения.

Сами помеченные документы удаляют штатно: Администрирование → Обслуживание, группа «Корректировка данных», команда удаления помеченных объектов. Она проверяет, нет ли на документ ссылок, и не удаляет то, на что ссылаются другие данные.

Непосредственное удаление

Метод Объект.Удалить() удаляет документ сразу, без проверки ссылочной целостности. Если на документ ссылался платёж или счёт-фактура, в них останутся битые ссылки вида «<Объект не найден>».

Сделайте резервную копию базы перед программным удалением — восстановить удалённый документ без неё нельзя.

Безопаснее удалить программно с контролем ссылок через глобальный метод УдалитьОбъекты():

Удаляемые = Новый Массив;
Удаляемые.Добавить(Ссылка);

Найденные = Неопределено;
УдалитьОбъекты(Удаляемые, Истина, Найденные);
// Если на документ есть ссылки, он не удалится, а в «Найденные» попадёт таблица ссылающихся объектов

Узнать, кто ссылается на документ, заранее можно методом НайтиПоСсылкам(Удаляемые) — он возвращает таблицу значений со ссылающимися объектами.

Для непосредственного удаления у пользователя должно быть право «Удаление» на документ. В типовых конфигурациях у обычных ролей его нет, поэтому код, запущенный от имени бухгалтера, упадёт с ошибкой Нарушение прав доступа!. Для служебной операции на сервере используют УстановитьПривилегированныйРежим(Истина).

Обращение к документу из модуля формы

В форме документа есть реквизит Объект, но это не ДокументОбъект, а его отображение для клиента (ДанныеФормыСтруктура). Методы Записать(), УстановитьПометкуУдаления() у него вызвать нельзя.

Три типовых случая:

  1. Записать или провести открытую форму — метод формы, а не объекта:
&НаКлиенте
Процедура ПровестиИЗакрыть(Команда)
	ПараметрыЗаписи = Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение);
	Если Записать(ПараметрыЗаписи) Тогда
		Закрыть();
	КонецЕсли;
КонецПроцедуры
  1. Нужен объект в серверной процедуре формы — функция РеквизитФормыВЗначение("Объект") даёт полноценный ДокументОбъект, после правок обратно — ЗначениеВРеквизитФормы(ДокОбъект, "Объект").

  2. Работа с другим документом, не открытым в форме — передайте ссылку в процедуру с директивой &НаСервереБезКонтекста или в общий модуль с флагом «Сервер» и там вызывайте ПолучитьОбъект().

&НаСервереБезКонтекста
Процедура ПровестиДокументНаСервере(Ссылка)
	Объект = Ссылка.ПолучитьОбъект();
	Объект.Записать(РежимЗаписиДокумента.Проведение);
КонецПроцедуры

Если вызвать Ссылка.ПолучитьОбъект() в процедуре &НаКлиенте, тонкий клиент выдаст ошибку Метод объекта не обнаружен (ПолучитьОбъект).

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

Частые вопросы

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