Создать документ 1С программно приходится во многих задачах: заказы с сайта и маркетплейсов превращаются в документы, реализация оформляется по заказу одной кнопкой, регламентное задание ночью закрывает отгрузки, данные переносятся из Excel, а по подписке на событие при записи одного документа создаётся другой. Схема везде одна: Документы.Имя.СоздатьДокумент(), заполнение шапки и табличных частей, Записать(). Ниже — готовые процедуры для платформы 8.3: универсальное создание по данным, реализация по заказу клиента в УТ 11.4 с обработкой ошибок, пакетное оформление с транзакцией и вызов из формы.
Два правила до кода. В 8.3 документ программно создают там, где доступен объект документа: на сервере, в толстом клиенте и внешнем соединении. В тонком клиенте нет ни Документы, ни запросов, поэтому код создания кладут в серверный общий модуль, модуль менеджера документа или модуль объекта обработки, а форма вызывает его через сервер. Имена документа, реквизитов и табличных частей берите в конфигураторе, в ветке «Документы», а что документ делает при заполнении, смотрите в его модуле объекта, в процедуре ОбработкаЗаполнения.
Функция ниже — программное создание документа 1С любого вида по имени: реквизиты шапки передаются структурой, строки — таблицей значений или массивом структур для каждой табличной части. Подходит для загрузок и обменов.
// Создаёт документ по данным и записывает его. Возвращает ссылку на документ.
// ИмяДокумента - Строка - имя из ветки «Документы», например "ПриобретениеТоваровУслуг".
// Реквизиты - Структура - значения шапки: ключ = имя реквизита (можно передать и Дата).
// Строки - Соответствие - ключ: имя табличной части, значение: ТаблицаЗначений или массив структур.
// Провести - Булево - Истина: запись с проведением, Ложь: просто запись.
Функция СоздатьДокументПоДанным(ИмяДокумента, Реквизиты, Строки = Неопределено, Провести = Ложь) Экспорт
ДокументОбъект = Документы[ИмяДокумента].СоздатьДокумент();
// Дату - до заполнения: от неё могут зависеть умолчания (курсы, ставки)
ДокументОбъект.Дата = ТекущаяДатаСеанса();
// СоздатьДокумент() не вызывает ОбработкаЗаполнения, а типовые ставят умолчания именно там
ДокументОбъект.Заполнить(Неопределено);
ЗаполнитьЗначенияСвойств(ДокументОбъект, Реквизиты);
Если Строки <> Неопределено Тогда
Для Каждого ОписаниеТЧ Из Строки Цикл
ТабличнаяЧасть = ДокументОбъект[ОписаниеТЧ.Ключ];
Если ТипЗнч(ОписаниеТЧ.Значение) = Тип("ТаблицаЗначений") Тогда
ТабличнаяЧасть.Загрузить(ОписаниеТЧ.Значение); // колонки сопоставляются по именам
Иначе
ТабличнаяЧасть.Очистить();
Для Каждого ДанныеСтроки Из ОписаниеТЧ.Значение Цикл
ЗаполнитьЗначенияСвойств(ТабличнаяЧасть.Добавить(), ДанныеСтроки);
КонецЦикла;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если Провести Тогда
// Записать() из кода проверку заполнения не запускает - проверяем сами
ТекстОшибок = ОшибкиЗаполнения(ДокументОбъект);
Если ЗначениеЗаполнено(ТекстОшибок) Тогда
ВызватьИсключение ТекстОшибок;
КонецЕсли;
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Иначе
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Возврат ДокументОбъект.Ссылка;
КонецФункции
// Проверяет заполнение и возвращает тексты сообщений об ошибках ("" - ошибок нет).
Функция ОшибкиЗаполнения(ДокументОбъект) Экспорт
БылоСообщений = ПолучитьСообщенияПользователю().Количество();
Если ДокументОбъект.ПроверитьЗаполнение() Тогда
Возврат "";
КонецЕсли;
// Причины лежат в очереди сообщений: в регламентном задании их иначе никто не прочитает
Сообщения = ПолучитьСообщенияПользователю();
Тексты = Новый Массив;
Для Индекс = БылоСообщений По Сообщения.Количество() - 1 Цикл
Тексты.Добавить(Сообщения[Индекс].Текст);
КонецЦикла;
Если Тексты.Количество() = 0 Тогда
Тексты.Добавить(НСтр("ru = 'Документ не прошёл проверку заполнения'"));
КонецЕсли;
Возврат СтрСоединить(Тексты, Символы.ПС);
КонецФункции
Вызов для черновика с табличной частью «Товары»:
// Пример вызова: черновик без проведения. Имена реквизитов и колонок - из вашего документа.
Товары = Новый ТаблицаЗначений;
Товары.Колонки.Добавить("Номенклатура");
Товары.Колонки.Добавить("Количество");
Товары.Колонки.Добавить("Цена");
НоваяСтрока = Товары.Добавить();
НоваяСтрока.Номенклатура = Номенклатура;
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 150;
Строки = Новый Соответствие;
Строки.Вставить("Товары", Товары);
Реквизиты = Новый Структура("Склад, Комментарий", Склад, "Загрузка из файла");
Ссылка = СоздатьДокументПоДанным(ИмяДокумента, Реквизиты, Строки);
СоздатьДокумент() возвращает новый объект в памяти: ссылка, дата и номер пустые, в базе документа нет до вызова Записать().Заполнить(Неопределено) запускает ОбработкаЗаполнения модуля документа — так же, как кнопка «Создать» в списке без отбора. Сам СоздатьДокумент() этот обработчик не вызывает, а типовые ставят умолчания именно там: в УТ 11.4 реализация получает организацию, валюту, склад, менеджера и кассу в процедуре ИнициализироватьДокумент, которую вызывает ОбработкаЗаполнения.ЗаполнитьЗначенияСвойств переносит одноимённые значения и молча пропускает ключи, которых у документа нет. Опечатка в имени реквизита ошибки не даст — значение просто потеряется.Загрузить() удаляет прежние строки табличной части и сопоставляет колонки по именам; Добавить() с ЗаполнитьЗначенияСвойств делает то же построчно.ПроверитьЗаполнение() обязателен: при записи из кода ОбработкаПроверкиЗаполнения не вызывается, её запускает только форма или этот метод. Причины ошибок уходят в очередь сообщений, и ОшибкиЗаполнения собирает их в текст для исключения и журнала.Записать(РежимЗаписиДокумента.Проведение) пишет и проводит документ в одной транзакции. Режим проведения по умолчанию — неоперативный.Дату задавайте явно. Рабочую дату платформа подставляет при открытии формы нового документа, а у объекта из СоздатьДокумент() дата пустая. Берите ТекущаяДатаСеанса(): она учитывает часовой пояс сеанса, а ТекущаяДата() возвращает время компьютера, на котором выполняется код.
Новый документ, созданный программно, получает номер при первой записи, если включена автонумерация и номер пустой. Нужен раньше — вызовите УстановитьНовыйНомер(). При нумерации в пределах периода номер зависит от даты, а в конфигурациях на БСП ещё и от организации: подписка на событие ПриУстановкеНовогоНомера добавляет префиксы информационной базы и организации. Отсюда порядок: сначала дата и организация, потом номер. Для ориентира: у реализации в УТ 11.4 номер строковый, 11 символов, уникален в пределах года.
// Дата, организация и номер нового документа - до записи.
// Номер - необязательный: например, номер из внешней системы.
Процедура УстановитьДатуИНомер(ДокументОбъект, ДатаДокумента, Организация, Номер = "") Экспорт
// Сначала дата и организация: от них зависят период нумерации и префикс номера
ДокументОбъект.Дата = ДатаДокумента;
ДокументОбъект.Организация = Организация;
// Дата без времени (например, из файла): при записи документ встанет последним в этом дне
Если ДатаДокумента = НачалоДня(ДатаДокумента) Тогда
ДокументОбъект.УстановитьВремя(РежимАвтоВремя.Последним);
КонецЕсли;
Если ЗначениеЗаполнено(Номер) Тогда
// Чужой номер: уникальность в периоде платформа проверит при записи
ДокументОбъект.Номер = Номер;
Иначе
// Номер нужен до записи - получаем сразу; префиксы базы и организации добавит БСП
ДокументОбъект.УстановитьНовыйНомер();
КонецЕсли;
КонецПроцедуры
Ссылка нового документа до записи тоже пустая. Если её нужно положить в другой объект раньше, назначьте её сами: УстановитьСсылкуНового(Документы.Имя.ПолучитьСсылку()) — без параметра ПолучитьСсылку() даёт новую ссылку.
Заполнение документа программно на основании другого идёт через Заполнить(Данные): метод передаёт значение любого типа в параметр ДанныеЗаполнения обработчика ОбработкаЗаполнения. Так типовые документы поддерживают ввод на основании: в УТ 11.4 реализация принимает ссылку на заказ клиента (шапка из заказа, строки по неотгруженному остатку) или структуру с ключами Партнер, РеквизитыШапки, Товары. Как написать такой обработчик в своём документе, разобрано в статье про обработку заполнения, здесь — сторона вызова.
Функция написана по коду УТ 11.4; в КА 2.5 реализация заполняется по заказу тем же вызовом, отличия — в дате и скидках. Положите её в серверный общий модуль или модуль объекта обработки рядом с ОшибкиЗаполнения из первого листинга.
// Создаёт и проводит реализацию по заказу клиента (УТ 11.4).
// Возвращает ссылку на реализацию или Неопределено, если по заказу нечего отгружать.
// Подробности ошибки пишет в журнал регистрации и вызывает исключение с понятным текстом.
Функция ОформитьРеализациюПоЗаказу(Заказ, ДатаРеализации = Неопределено) Экспорт
Попытка
Реализация = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
// ОбработкаЗаполнения типовой: шапка из заказа, строки - по неотгруженному остатку.
// Непроведённый заказ или неподходящий статус - исключение из типового кода.
Реализация.Заполнить(Заказ);
Если Реализация.Товары.Количество() = 0 Тогда
Возврат Неопределено; // всё уже отгружено
КонецЕсли;
// Заполнение само ставит дату (УТ 11.4) или оставляет пустой - своя дата только после него
Если ЗначениеЗаполнено(ДатаРеализации) Тогда
Реализация.Дата = ДатаРеализации;
ИначеЕсли Не ЗначениеЗаполнено(Реализация.Дата) Тогда
Реализация.Дата = ТекущаяДатаСеанса();
КонецЕсли;
Реализация.Комментарий = НСтр("ru = 'Создана автоматически по заказу'");
ТекстОшибок = ОшибкиЗаполнения(Реализация);
Если ЗначениеЗаполнено(ТекстОшибок) Тогда
ВызватьИсключение ТекстОшибок;
КонецЕсли;
// Запись и проведение - одна транзакция: не провелась, значит и не записалась
Реализация.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации(НСтр("ru = 'Продажи.Оформление реализаций'"), УровеньЖурналаРегистрации.Ошибка,
Метаданные.Документы.РеализацияТоваровУслуг, Заказ, ОбработкаОшибок.ПодробноеПредставлениеОшибки(Инфо));
ВызватьИсключение СтрШаблон(НСтр("ru = 'Реализация по заказу %1 не создана: %2'"),
Заказ, ОбработкаОшибок.КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;
Возврат Реализация.Ссылка;
КонецФункции
Заполнить(Заказ) вызывает исключение, если заказ не проведён или находится в неподходящем статусе, — проверки встроены в типовую. Попытка ловит их вместе с ошибками проведения.Неопределено.Заполнить(), иначе её перезапишут.ОбработкаОшибок есть с платформы 8.3.17, на старых версиях используйте глобальные ПодробноеПредставлениеОшибки и КраткоеПредставлениеОшибки.При заполнении из кода обработчики формы не срабатывают, а в типовых именно они пересчитывают количество из упаковок, ставку и сумму НДС, сумму строки. В УТ 11.4 и КА 2.5 форма вызывает ОбработкаТабличнойЧастиКлиент.ОбработатьСтрокуТЧ со структурой действий, а для серверного кода есть парный ОбработкаТабличнойЧастиСервер.ОбработатьСтрокуТЧ — типовая сама применяет его к строкам объекта документа. Пример — реализация без заказа:
// Реализация без заказа (УТ 11.4): шапку заполняет типовая обработка заполнения по партнёру,
// строки добавляем сами и пересчитываем так же, как это делает форма документа.
// Товары - ТаблицаЗначений с колонками Номенклатура, Количество, Цена.
// Возвращает незаписанный объект: записать или провести решает вызывающий код.
Функция НоваяРеализацияБезЗаказа(Партнер, Склад, Товары) Экспорт
Реализация = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
Реализация.Дата = ТекущаяДатаСеанса();
// Эти ключи понимает ОбработкаЗаполнения реализации:
// Партнер - подбор соглашения и условий продаж, РеквизитыШапки - значения шапки
ДанныеЗаполнения = Новый Структура;
ДанныеЗаполнения.Вставить("Партнер", Партнер);
ДанныеЗаполнения.Вставить("РеквизитыШапки", Новый Структура("Склад", Склад));
Реализация.Заполнить(ДанныеЗаполнения);
ПараметрыНДС = ОбработкаТабличнойЧастиКлиентСервер.ПараметрыПересчетаСуммыНДСВСтрокеТЧ(Реализация);
ПараметрыСтавки = Новый Структура("НалогообложениеНДС, Дата, Организация, ХозяйственнаяОперация",
Реализация.НалогообложениеНДС, Реализация.Дата, Реализация.Организация, Реализация.ХозяйственнаяОперация);
Кэш = ОбработкаТабличнойЧастиКлиентСервер.ПолучитьСтруктуруКэшируемыеЗначения();
Для Каждого Товар Из Товары Цикл
СтрокаТоваров = Реализация.Товары.Добавить();
СтрокаТоваров.Номенклатура = Товар.Номенклатура;
СтрокаТоваров.КоличествоУпаковок = Товар.Количество; // упаковка пустая - базовые единицы
СтрокаТоваров.Цена = Товар.Цена;
СтрокаТоваров.Склад = Реализация.Склад;
// Без этого Количество, СтавкаНДС, Сумма и СуммаНДС в строке останутся пустыми
Действия = Новый Структура;
Действия.Вставить("ПересчитатьКоличествоЕдиниц");
Действия.Вставить("ЗаполнитьСтавкуНДС", ПараметрыСтавки);
Действия.Вставить("ПересчитатьСумму");
Действия.Вставить("ПересчитатьСуммуНДС", ПараметрыНДС);
Действия.Вставить("ПересчитатьСуммуСНДС", ПараметрыНДС);
ОбработкаТабличнойЧастиСервер.ОбработатьСтрокуТЧ(СтрокаТоваров, Действия, Кэш);
КонецЦикла;
Возврат Реализация;
КонецФункции
Итог СуммаДокумента реализация пересчитывает сама перед записью. Проведётся ли такой документ, зависит от настроек базы (соглашения, ордерная схема на складе), поэтому функция возвращает объект без записи — проверьте его на копии базы.
Записать(РежимЗаписиДокумента.Проведение) сама открывает транзакцию: ПередЗаписью, ПриЗаписи и ОбработкаПроведения выполняются внутри неё. Отказ при проведении откатывает и запись: новый документ не появится, у существующего в базе останется прежняя версия. Своя транзакция нужна, когда несколько документов должны появиться вместе. Обратная задача — пакет, где ошибка одного заказа не должна останавливать остальные.
// Регламентное задание: реализации по проведённым заказам за последние дни.
// Каждый документ пишется в своей транзакции: ошибка по одному заказу не мешает остальным.
Процедура ОформитьРеализацииПоЗаказам(ГлубинаДней = 30) Экспорт
Запрос = Новый Запрос(
"ВЫБРАТЬ
| ЗаказКлиента.Ссылка КАК Заказ
|ИЗ
| Документ.ЗаказКлиента КАК ЗаказКлиента
|ГДЕ
| ЗаказКлиента.Проведен
| И ЗаказКлиента.Статус В (&Статусы)
| И ЗаказКлиента.Дата >= &НачалоПериода
|
|УПОРЯДОЧИТЬ ПО
| ЗаказКлиента.Дата");
// Статусы, из которых типовая разрешает ввод на основании (зависят от настроек заказов)
Запрос.УстановитьПараметр("Статусы", Документы.ЗаказКлиента.ДопустимыеСтатусыВводаНаОсновании());
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДатаСеанса()) - ГлубинаДней * 86400);
Создано = 0;
Ошибок = 0;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Попытка
Если ОформитьРеализациюПоЗаказу(Выборка.Заказ) <> Неопределено Тогда
Создано = Создано + 1;
КонецЕсли;
Исключение
Ошибок = Ошибок + 1; // подробности уже в журнале регистрации
КонецПопытки;
КонецЦикла;
ЗаписьЖурналаРегистрации(НСтр("ru = 'Продажи.Оформление реализаций'"), УровеньЖурналаРегистрации.Информация, , ,
СтрШаблон(НСтр("ru = 'Создано реализаций: %1, ошибок: %2'"), Создано, Ошибок));
КонецПроцедуры
// Реализации по нескольким заказам: появляются все вместе или ни одной.
Функция ОформитьРеализацииВсеИлиНичего(Заказы) Экспорт
Реализации = Новый Массив;
НачатьТранзакцию();
Попытка
Для Каждого Заказ Из Заказы Цикл
Реализация = ОформитьРеализациюПоЗаказу(Заказ);
Если Реализация = Неопределено Тогда
ВызватьИсключение СтрШаблон(НСтр("ru = 'По заказу %1 нечего отгружать'"), Заказ);
КонецЕсли;
Реализации.Добавить(Реализация);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Ничего не записано, ссылки из массива недействительны - отдаём ошибку наверх
ВызватьИсключение;
КонецПопытки;
Возврат Реализации;
КонецФункции
НачатьТранзакцию() непосредственно перед Попытка, ЗафиксироватьТранзакцию() последней строкой, в Исключение сначала ОтменитьТранзакцию(), затем ВызватьИсключение без параметров. Подробнее — в статье про транзакции. Запись в журнал регистрации по умолчанию независимая и откат переживает.Документы.ЗаказКлиента.ДопустимыеСтатусыВводаНаОсновании() — функция есть в УТ 11.4 и КА 2.5. Точнее отбирать по остатку «к оформлению»: в УТ 11.4 это регистр ЗаказыКлиентов, в КА 2.5 — РаспоряженияНаОтгрузку. Запрос удобно отладить в консоли запросов.Команда формы выполняется на клиенте, а документы создаются на сервере. Во внешней обработке функции удобно держать в модуле объекта, а из формы получать объект через РеквизитФормыВЗначение. У обработки табличная часть «Заказы» с колонками Заказ, Реализация и Ошибка.
// Форма внешней обработки «Оформление реализаций». Табличная часть обработки Заказы:
// Заказ (ДокументСсылка.ЗаказКлиента), Реализация (ДокументСсылка.РеализацияТоваровУслуг), Ошибка (Строка).
// ОформитьРеализациюПоЗаказу и ОшибкиЗаполнения лежат в модуле объекта обработки.
&НаКлиенте
Процедура ОформитьРеализации(Команда)
Если Объект.Заказы.Количество() = 0 Тогда
ПоказатьПредупреждение(, НСтр("ru = 'Добавьте заказы в список'"));
Возврат;
КонецЕсли;
Итог = ОформитьРеализацииНаСервере();
ПоказатьОповещениеПользователя(НСтр("ru = 'Оформление реализаций'"), , Итог);
КонецПроцедуры
&НаСервере
Функция ОформитьРеализацииНаСервере()
// Документы, запросы и модуль объекта обработки доступны только на сервере
Обработка = РеквизитФормыВЗначение("Объект");
Создано = 0;
Для Каждого СтрокаЗаказа Из Обработка.Заказы Цикл
Попытка
СтрокаЗаказа.Реализация = Обработка.ОформитьРеализациюПоЗаказу(СтрокаЗаказа.Заказ);
Если ЗначениеЗаполнено(СтрокаЗаказа.Реализация) Тогда
СтрокаЗаказа.Ошибка = "";
Создано = Создано + 1;
Иначе
СтрокаЗаказа.Ошибка = НСтр("ru = 'Нечего отгружать'");
КонецЕсли;
Исключение
СтрокаЗаказа.Ошибка = ОбработкаОшибок.КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЦикла;
// Вернуть изменённые строки в форму
ЗначениеВРеквизитФормы(Обработка, "Объект");
Возврат СтрШаблон(НСтр("ru = 'Создано реализаций: %1 из %2'"), Создано, Обработка.Заказы.Количество());
КонецФункции
Если заказов сотни, вынесите цикл в фоновое задание: серверный вызов из формы держит клиента до конца. Как добавить команду и кнопку, описано в статье про команды формы.
&НаСервере или серверный общий модуль.Заполнить(), а умолчания типовых ставит ОбработкаЗаполнения.ПроверитьЗаполнение().ОбработатьСтрокуТЧ.Заполнить() по непроведённому заказу; для неподходящего статуса текст «…находится в статусе … Ввод на основании разрешен только в статусе …». Это исключения, без Попытка они прервут весь пакет.СкидкиРассчитаны = Ложь. Перед записью вызовите СкидкиНаценкиСервер.РассчитатьПоРеализацииТоваровУслугПоЗаказу(Реализация) — так делает форма документа. В КА 2.5 флаг ставит само заполнение, а эта функция там не экспортная.ПроверитьНомерДокументаПоДатеИОрганизации перед записью очищает номер записанного документа, если дата ушла в другой период нумерации или сменился префикс организации.Создайте объект и вызовите Заполнить(Основание): основание попадёт в ОбработкаЗаполнения, и документ заполнится так же, как по команде «Создать на основании». Если документ такое основание не поддерживает, обработчик ничего не сделает — проверяйте результат, например число строк.
Код тот же, меняется место. Во внешней обработке — модуль объекта, из формы через РеквизитФормыВЗначение. В регламентном задании — серверный общий модуль, как процедура ОформитьРеализацииПоЗаказам. Права на создание и проведение нужны пользователю, от имени которого работает сеанс; у регламентного задания он указывается в его настройках.
Его записали двумя вызовами: сначала Записать(РежимЗаписиДокумента.Запись), потом проведение, и второй вызов упал. Один вызов с режимом Проведение так не оставит: при отказе не сохраняется ничего. Причины смотрите в сообщениях пользователю и в журнале регистрации.
В тонком и веб-клиенте — нет: объекта документа там нет. Вызывайте серверную процедуру формы или общий модуль с флажком «Вызов сервера» и передавайте туда простые значения и ссылки, а не объекты. Толстый клиент объекты видит, но и там код создания лучше держать на сервере.
Если документы должны создаваться из заказов сайта, маркетплейсов или файлов — с проверками, журналом ошибок и повторной обработкой, — это отдельная доработка с тестами на копии базы. Такие задачи берут исполнители площадки: обработки на заказ, обмен с сайтом и другие услуги. Разместите задачу бесплатно: оценка тоже бесплатная, ответ — в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.