При доработке типовых конфигураций 1С, особенно таких масштабных систем, как «Управление производственным предприятием» (УПП 1.3), разработчики часто сталкиваются с задачей заполнения документов на основании других объектов. Классический пример — создание документа РеализацияТоваровУслуг на основании ЗаказПокупателя.
В этой статье мы подробно разберем, как правильно написать запрос к табличной части документа-основания, перенести данные в новый документ, избежать распространенных ошибок кодинга и обойти методологические «подводные камни», связанные с учетом остатков и резервов в УПП.
Рассмотрим ситуацию: нам необходимо программно скопировать данные из табличной части Товары документа ЗаказПокупателя в аналогичную табличную часть создаваемого документа РеализацияТоваровУслуг. Нам нужно написать процедуру, которая будет вызываться, например, в обработчике ОбработкаЗаполнения.
Начинающие разработчики часто совершают концептуальную ошибку в тексте запроса, пытаясь выбрать данные из табличной части самого создаваемого документа реализации, а не документа-основания. Посмотрим на ошибочный вариант запроса:
// ОШИБОЧНЫЙ ЗАПРОС: выборка идет из документа реализации!
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура,
| Товары.Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары";
Проанализируем этот код. Этот запрос пытается прочитать данные из еще не записанного (или вообще из всех существующих) документа реализации. Чтобы получить данные из источника, запрос должен быть адресован к табличной части документа-основания (в нашем случае — к ЗаказПокупателя), а в качестве условия необходимо передать конкретную ссылку на этот документ.
Для корректного получения данных мы должны обратиться к таблице Документ.ЗаказПокупателя.Товары и установить фильтр по реквизиту Ссылка. Разберем правильную реализацию этого механизма по шагам.
Рассмотрим готовый программный код процедуры переноса данных:
Процедура СкопироватьТоварыИзЗаказа(ДокументОснование)
// Проверяем, что документ-основание заполнен
Если Не ЗначениеЗаполнено(ДокументОснование) Тогда
Возврат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ДокументОснование", ДокументОснование);
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказТовары.НомерСтроки,
| ЗаказТовары.Номенклатура,
| ЗаказТовары.Количество,
| ЗаказТовары.ЕдиницаИзмерения,
| ЗаказТовары.Коэффициент,
| ЗаказТовары.Цена,
| ЗаказТовары.ПроцентСкидкиНаценки,
| ЗаказТовары.Сумма
|ИЗ
| Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
|ГДЕ
| ЗаказТовары.Ссылка = &ДокументОснование";
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Товары.Добавить();
// Заполнение базовых реквизитов из выборки
НоваяСтрока.Номенклатура = Выборка.Номенклатура;
НоваяСтрока.Количество = Выборка.Количество;
НоваяСтрока.ЕдиницаИзмерения = Выборка.ЕдиницаИзмерения;
НоваяСтрока.Коэффициент = Выборка.Коэффициент;
НоваяСтрока.Цена = Выборка.Цена;
НоваяСтрока.ПроцентСкидкиНаценки = Выборка.ПроцентСкидкиНаценки;
НоваяСтрока.Сумма = Выборка.Сумма;
// Установка связи с заказом в строке (критично для УПП)
НоваяСтрока.ЗаказПокупателя = ДокументОснование;
КонецЦикла;
КонецПроцедуры
Если обратить внимание на итоговый код автора в Сообщении 8, то там присутствует грубая опечатка:
НоваяСтрока.Номенклатура = Выборка.НомерСтроки;
НоваяСтрока.Номенклатура = Выборка.Номенклатура;
В первой строчке переменной с типом «СправочникСсылка.Номенклатура» ошибочно присваивается числовой номер строки. Во второй строчке эта ошибка перезаписывается правильной ссылкой, однако такой код засоряет систему, замедляет работу и может вызвать предупреждения при проверке конфигурации. Пишите код аккуратно!
Для того чтобы сделать код более компактным, надежным и избавиться от необходимости вручную прописывать каждую связь «приемник-источник», мы можем использовать встроенную функцию платформы 1С ЗаполнитьЗначенияСвойств(). Она автоматически сопоставляет имена реквизитов в выборке запроса и в строке табличной части документа реализации.
Посмотрим, насколько лаконичнее становится обход выборки:
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Товары.Добавить();
// Платформа сама найдет совпадающие поля и перенесет их значения
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
// Вручную заполняем только те реквизиты, которых не было в выборке Заказа
НоваяСтрока.ЗаказПокупателя = ДокументОснование;
КонецЦикла;
В ходе обсуждения на форуме прозвучало предложение полностью отказаться от цикла выборки и загрузить данные напрямую через Таблицу Значений (ТЗ):
ТЗТовары = РезультатЗапроса.Выгрузить();
Товары.Загрузить(ТЗТовары);
Выясним преимущества и недостатки этого метода.
Преимущества:
Недостатки и опасности метода:
Загрузить() полностью очищает табличную часть перед вставкой новых строк. Если в документе реализации уже были введены какие-то товары вручную, они будут безвозвратно удалены.В сообщении №7 автор признается, почему он решил переписать стандартное заполнение: «Заполнить по заказу покупателя документ реализация товаров и услуг не переносит все товары из табличной части (что-то с остатками, программа хочет каких-то настроек)».
Внимание! Это критическая методологическая ошибка. Давайте разберемся, почему стандартный типовой механизм УПП «выкидывал» часть товаров при заполнении.
При вводе Реализации на основании Заказа система производит сложный анализ регистров накопления:
Если программа «не переносит» какие-то товары, это означает одно из трех:
Если мы жестко скопируем строки кодом напрямую, мы столкнемся со следующими проблемами:
«Недостаточно товара на складе...». Пользователю все равно придется вручную удалять строки, по которым нет остатков.Если бизнес-процесс компании действительно требует выписки накладных до фактического поступления товаров, правильнее использовать штатные настройки системы, а не ломать логику кодом:
Закрытие заказов покупателей.РеализацияТоваровУслуг будет выписываться без жесткого контроля остатков, а фактическое списание и контроль физического наличия на складе возьмет на себя расходный ордер, проводимый кладовщиком.
// Пример вызова заполнения счетов в УПП
ЗаполнениеУчетныхДанных.ЗаполнитьСчетаУчетаВТЧ(НоваяСтрока, "Товары", ЭтотОбъект);
Рассмотрев все варианты, мы пришли к выводу, что простое копирование строк — это лишь вершина айсберга. При работе с УПП всегда важно помнить о движениях по регистрам и сохранении целостности партионного учета!