Когда мы разрабатываем функционал в 1С:Предприятии 8.3, часто возникает задача создать новый документ, тип которого должен быть таким же, как у уже существующего документа, на который у нас есть только ссылка. Например, нам передали ссылку на документ "РасходнаяНакладная", и нам нужно создать еще один пустой документ "РасходнаяНакладная". Разберем, как это можно реализовать, избегая распространенных ошибок и понимая внутренние механизмы платформы.
Прежде чем мы перейдем к решению, давайте разберемся в ключевой терминологии 1С, связанной с документами, поскольку их правильное понимание критически важно для эффективной работы с платформой.
ДокументСсылка.РеализацияТоваровУслуг)Это облегченный тип, который используется для хранения ссылок на объекты базы данных. Представьте ее как уникальный адрес документа в базе данных. Ссылка содержит только идентификатор документа (УникальныйИдентификатор) и основные, служебные свойства. Мы используем ссылки в реквизитах других объектов, в параметрах процедур и функций, а также для отображения документов в списках. Ключевое отличие: ДокументСсылка не содержит всех данных документа (например, табличные части) и не предназначена для непосредственного изменения или записи данных. Она лишь "указывает" на документ.
ДокументОбъект.РеализацияТоваровУслуг)Это "полноценный" экземпляр документа, который загружается из базы данных со всеми его реквизитами, табличными частями и прочими данными. Мы получаем ДокументОбъект, когда нам нужно прочитать, изменить, записать, провести или удалить конкретный документ. Получение ДокументОбъекта из ДокументСсылки осуществляется методом ПолучитьОбъект(). Работа с ДокументОбъектом более ресурсоемка, поскольку требует загрузки всех данных в оперативную память.
Документы.РеализацияТоваровУслуг)Это объект, который представляет вид документа в целом, как элемент метаданных конфигурации (то есть, не конкретный документ, а "тип документа"). Через менеджер документа мы выполняем глобальные операции, такие как создание нового документа данного типа (метод СоздатьДокумент()), поиск документов по определенным критериям (например, НайтиПоНомеру(), НайтиПоРеквизиту()) или получение пустой ссылки на документ. Менеджер документов является точкой входа для работы со всеми документами определенного вида.
Важно понимать, что 1С:Предприятие не является чисто объектно-ориентированным языком в классическом понимании (как Java или C#). Поэтому такие формулировки, как "Класс Документы (Объект)", могут быть немного некорректными. Правильнее говорить о типах платформы: тип ДокументСсылка, тип ДокументОбъект и объект МенеджерДокумента, который представляет собой метаданные типа.
Теперь, когда мы разобрались с терминологией, рассмотрим основной сценарий: как создать новый документ того же типа, что и документ, на который у нас есть ДокументСсылка. Задача состоит в том, чтобы динамически определить тип документа из ссылки и затем использовать менеджер документов для создания нового экземпляра.
Разберем решение по шагам:
ДокументСсылку в серверную процедуру, так как работа с метаданными и создание объектов документов происходит на сервере.Метаданные у ДокументСсылки, чтобы получить объект метаданных, а затем свойство Имя этого объекта, чтобы узнать строковое имя вида документа (например, "РеализацияТоваровУслуг").Документы (это коллекция всех менеджеров документов).СоздатьДокумент(): У менеджера документов вызываем метод СоздатьДокумент(), который вернет нам новый, еще не записанный в базу ДокументОбъект.Записать().Посмотрим на пример кода:
&НаКлиенте
Процедура СоздатьДокумент(Команда)
// Предположим, ДокументСсылка получена из какого-то источника,
// например, из текущего элемента списка или путем поиска.
// Для примера, получим ссылку на первый попавшийся документ "РеализацияТоваровУслуг"
// (в реальной задаче она будет передаваться)
ДокументСсылка = Документы.РеализацияТоваровУслуг.НайтиПоНомеру("000000001", НачалоГода(ТекущаяДата()));
Если ДокументСсылка.Пустая() Тогда
Сообщить("Для примера не найдена ссылка на документ. Пожалуйста, убедитесь, что документ с номером 000000001 существует.");
Возврат;
КонецЕсли;
СоздатьДокументНаСервере(ДокументСсылка);
КонецПроцедуры
&НаСервере
Процедура СоздатьДокументНаСервере(ДокументСсылка)
// Шаг 1: Убедимся, что ссылка не пустая, прежде чем пытаться получить метаданные
Если ДокументСсылка.Пустая() Тогда
Сообщить("Передана пустая ссылка на документ. Создание нового документа невозможно.");
Возврат;
КонецЕсли;
// Шаг 2: Получаем имя типа документа из метаданных ссылки
// Свойство Метаданные() возвращает объект МетаданныеОбъектДокумент.
// У этого объекта есть свойство Имя, которое и содержит строковое имя вида документа.
ИмяТипаДокумента = ДокументСсылка.Метаданные().Имя;
Сообщить("Определен тип документа: " + ИмяТипаДокумента);
// Шаг 3 и 4: Через менеджер документов обращаемся к конкретному виду документа по его имени
// и вызываем метод СоздатьДокумент().
// Конструкция Документы[ИмяТипаДокумента] позволяет динамически получить
// менеджер нужного вида документа, не зная его имя заранее.
НовыйДокОбъект = Документы[ИмяТипаДокумента].СоздатьДокумент();
// Шаг 5: Теперь у нас есть новый, еще не записанный в базу документ-объект.
// Мы можем заполнить его данными:
НовыйДокОбъект.Дата = ТекущаяДата();
НовыйДокОбъект.Номер = ""; // Сбросим номер, чтобы присвоился новый при записи, если настроена автонумерация
// Мы можем установить любые другие реквизиты, например:
// НовыйДокОбъект.Организация = ПолучитьЗначениеПоУмолчанию("ОсновнаяОрганизация");
// НовыйДокОбъект.Комментарий = "Документ создан по образцу другого документа";
// При необходимости, мы можем скопировать данные из исходного документа.
// Для этого нам сначала нужно получить исходный ДокументОбъект.
// ИсходныйОбъект = ДокументСсылка.ПолучитьОбъект();
// Если ИсходныйОбъект <> Неопределено Тогда
// НовыйДокОбъект.Организация = ИсходныйОбъект.Организация;
// НовыйДокОбъект.Контрагент = ИсходныйОбъект.Контрагент;
// // Перенос табличных частей может потребовать отдельной логики,
// // например, методом Заполнить() или поэлементным копированием.
// КонецЕсли;
// Обязательно записываем документ в базу данных.
// РежимЗаписиДокумента.Запись - сохраняет документ без проведения.
// РежимЗаписиДокумента.Проведение - сохраняет и проводит документ.
Попытка
НовыйДокОбъект.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Новый документ типа '" + ИмяТипаДокумента + "' успешно создан и записан. Ссылка: " + НовыйДокОбъект.Ссылка);
Исключение
Сообщить("Ошибка при записи нового документа: " + ОписаниеОшибки());
КонецПопытки;
// Если требуется провести документ сразу после записи:
// Попытка
// НовыйДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
// Сообщить("Новый документ успешно проведен.");
// Исключение
// Сообщить("Ошибка при проведении нового документа: " + ОписаниеОшибки());
// КонецПопытки;
КонецПроцедуры
Для более полного понимания работы с документами, рассмотрим их типичный жизненный цикл в 1С:Предприятии:
МенеджерДокумента.СоздатьДокумент(). На этом этапе формируется пустой ДокументОбъект.ДокументОбъект заполняется данными — реквизитами шапки и табличными частями. Это может быть сделано пользователем вручную или программно, например, путем копирования данных из другого документа или заполнения по определенному алгоритму.ДокументОбъект сохраняется в базу данных методом Записать(). При первой записи нового документа ему присваивается уникальный номер (если настроена автонумерация) и формируется уникальная ссылка (ДокументСсылка). Документ становится доступным для других пользователей и функционала системы.Записать(РежимЗаписиДокумента.Проведение). Проведенный документ влияет на остатки и обороты в регистрах.Помимо рассмотренного основного метода, существуют и другие важные подходы к работе с документами, которые мы также рассмотрим.
Если у нас уже есть ДокументСсылка на существующий документ, и нам нужно получить его ДокументОбъект для чтения или изменения данных, мы используем метод ПолучитьОбъект().
&НаСервере
Процедура ПолучитьСуществующийДокумент(ДокументСсылка)
Если ДокументСсылка.Пустая() Тогда
Сообщить("Ссылка пустая, объект получить невозможно.");
Возврат;
КонецЕсли;
// Получаем ДокументОбъект из ДокументСсылки
ДокОбъект = ДокументСсылка.ПолучитьОбъект();
Если ДокОбъект <> Неопределено Тогда
Сообщить("Объект документа '" + ДокОбъект.ПредставлениеДокумента + "' успешно получен.");
Сообщить("Номер: " + ДокОбъект.Номер + ", Дата: " + ДокОбъект.Дата);
// Теперь мы можем работать с реквизитами ДокОбъекта.
// Например, изменить его комментарий и записать:
// ДокОбъект.Комментарий = "Изменено программно";
// ДокОбъект.Записать();
Иначе
Сообщить("Не удалось получить объект по ссылке. Возможно, документ удален или не существует.");
КонецЕсли;
КонецПроцедуры
Помните, что ПолучитьОбъект() предназначен для работы с УЖЕ СУЩЕСТВУЮЩИМИ документами, а не для создания новых.
Если мы точно знаем тип документа, который хотим создать, и у нас нет исходной ссылки для определения типа, мы можем создать его напрямую через менеджер документов:
&НаСервере
Процедура СоздатьНовыйДокументСНуля()
// Предположим, мы хотим создать новый документ "РеализацияТоваровУслуг"
// Мы напрямую обращаемся к менеджеру документов для этого типа
НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
// Заполняем необходимые реквизиты.
// В зависимости от конфигурации и бизнес-логики, многие поля могут быть обязательными.
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Организация = Справочники.Организации.НайтиПоКоду("000000001"); // Пример получения организации
НовыйДокумент.Подразделение = Справочники.ПодразделенияОрганизаций.НайтиПоНаименованию("Основное подразделение"); // Пример
НовыйДокумент.Комментарий = "Новый документ создан программно с нуля.";
// Заполнение табличных частей, если необходимо:
// СтрокаТабличнойЧасти = НовыйДокумент.Товары.Добавить();
// СтрокаТабличнойЧасти.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00001");
// СтрокаТабличнойЧасти.Количество = 10;
// СтрокаТабличнойЧасти.Цена = 100;
// СтрокаТабличнойЧасти.Сумма = СтрокаТабличнойЧасти.Количество * СтрокаТабличнойЧасти.Цена;
Попытка
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Создан новый документ РеализацияТоваровУслуг. Ссылка: " + НовыйДокумент.Ссылка);
Исключение
Сообщить("Ошибка при создании документа с нуля: " + ОписаниеОшибки());
КонецПоцедуры
Иногда требуется создать документ с заранее известным уникальным идентификатором. Это может быть полезно, например, при загрузке данных из внешних систем, где нужно сохранить исходные идентификаторы для синхронизации.
&НаСервере
Процедура СоздатьДокументСЗаданнымGUID()
СтрокаGUID = "a1b2c3d4-e5f6-7890-1234-567890abcdef"; // Пример строкового представления GUID
НовыйGUID = Новый УникальныйИдентификатор(СтрокаGUID); // Преобразуем строку в объект УникальныйИдентификатор
// Сначала получаем ссылку, которую должен будет иметь новый документ,
// используя менеджер документов и заданный GUID.
СсылкаСЗаданнымGUID = Документы.РеализацияТоваровУслуг.ПолучитьСсылку(НовыйGUID);
// Создаем новый объект документа стандартным способом
НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
// Устанавливаем ссылку для нового документа перед записью.
// Метод УстановитьСсылкуНового() используется только для новых,
// еще не записанных объектов и позволяет присвоить им предопределенную ссылку.
НовыйДокумент.УстановитьСсылкуНового(СсылкаСЗаданнымGUID);
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Комментарий = "Документ создан с предварительно заданным GUID.";
// ... заполнение других реквизитов
Попытка
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Документ создан с заданным GUID. Ссылка: " + НовыйДокумент.Ссылка);
Исключение
Сообщить("Ошибка при создании документа с заданным GUID: " + ОписаниеОшибки());
КонецПоцедуры
Важно: Будьте осторожны при ручном задании GUID. Если GUID уже существует в базе данных для другого объекта того же типа, это приведет к ошибке при записи.
Если наша задача — создать новый документ, который будет очень похож на уже существующий (например, почти все реквизиты должны быть такими же), наиболее эффективным будет использование метода Скопировать(). Этот метод создает новый ДокументОбъект, который является полной копией исходного документа.
&НаСервере
Процедура СкопироватьДокумент(ИсходнаяСсылкаНаДокумент)
Если ИсходнаяСсылкаНаДокумент.Пустая() Тогда
Сообщить("Исходная ссылка пустая, копирование невозможно.");
Возврат;
КонецЕсли;
Попытка
// Метод Скопировать() вызывается у ДокументСсылки и возвращает
// новый ДокументОбъект, заполненный данными из исходного документа.
// Этот новый объект еще не записан в базу.
НовыйОбъект = ИсходнаяСсылкаНаДокумент.Скопировать();
// Мы можем изменить любые данные в новом объекте до его записи.
// Например, сбросить номер и установить текущую дату.
НовыйОбъект.Дата = ТекущаяДата();
НовыйОбъект.Номер = ""; // Сбросим номер, чтобы система присвоила новый при записи
НовыйОбъект.Комментарий = "Копия документа: " + ИсходнаяСсылкаНаДокумент.ПредставлениеДокумента;
НовыйОбъект.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Документ успешно скопирован. Новая ссылка: " + НовыйОбъект.Ссылка);
Исключение
Сообщить("Ошибка при копировании документа: " + ОписаниеОшибки());
КонецПоцедуры
Использование Скопировать() значительно упрощает создание однотипных документов и уменьшает объем кода, который нужно писать для заполнения реквизитов. Для более сложных сценариев пакетной генерации подойдёт готовый алгоритм создания нескольких документов на основании одного.
Метод НайтиПоНомеру() менеджера документа позволяет найти ссылку на документ по его номеру. Этот метод вызывается у менеджера конкретного вида документа.
&НаСервере
Процедура ПоискДокументаПоНомеру(НомерДокументаДляПоиска)
// Для примера ищем документ типа "ПоступлениеТоваровУслуг"
Менеджер = Документы.ПоступлениеТоваровУслуг;
// Важно: Если для вида документа в конфигураторе задана периодическая нумерация
// (например, в пределах года, месяца или дня), то для однозначного поиска
// необходимо передавать вторым параметром дату, определяющую период.
// Например, чтобы найти документ с номером "00000001" за текущий год:
// СсылкаНайденногоДокумента = Менеджер.НайтиПоНомеру(НомерДокументаДляПоиска, НачалоГода(ТекущаяДата()));
// Если нумерация непериодическая, или дата не критична (будет использована пустая дата),
// можно вызвать метод с одним параметром:
СсылкаНайденногоДокумента = Менеджер.НайтиПоНомеру(НомерДокументаДляПоиска);
Если Не СсылкаНайденногоДокумента.Пустая() Тогда
Сообщить("Найден документ: " + СсылкаНайденногоДокумента.ПредставлениеДокумента);
Иначе
Сообщить("Документ с номером '" + НомерДокументаДляПоиска + "' не найден.");
КонецЕсли;
КонецПроцедуры
Всегда учитывайте настройки нумерации в конфигураторе, чтобы избежать ошибок при поиске по номеру.
Мы рассмотрели различные подходы к созданию и получению документов в 1С:Предприятии 8.3. Понимание различий между ДокументСсылка, ДокументОбъект и МенеджерДокумента, а также знание соответствующих методов, позволит нам эффективно и корректно работать с данными в системе.