Документ в коде 1С 8.3: от поиска ссылки до проведения и удаления
Содержание
Обратиться к документу в 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 = Ссылка2 Тогда. Сравнение по номеру ненадёжно: номера повторяются в разных периодах и могут меняться.Почему после записи в обработке не сработали пересчёты, как в форме?
Как обратиться к документу из внешней обработки?
Документы.ИмяДокумента, в серверной процедуре формы обработки. Внешняя обработка работает с правами пользователя, который её запустил.