Как вывести склады из табличной части документа "Реализация товаров и услуг" в печатную форму?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В процессе работы с документом "Реализация товаров и услуг" часто возникает потребность в модификации его печатных форм. Одна из таких задач – вывод информации о складах, с которых отгружается товар, непосредственно в табличную часть печатной формы. Эта задача усложняется, когда по одной номенклатурной позиции в документе может быть указано несколько разных складов, и необходимо корректно отобразить каждый из них или их совокупность.

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

Анализ исходного кода и постановка задачи

Прежде всего, давайте проанализируем текущую функцию печати ПечатьДокумента(), предоставленную в сообщении 5. Наша цель – добавить в табличную часть печатной формы колонку "Склад", которая будет содержать информацию о складе для каждой позиции номенклатуры. Важно учитывать, что в документе "Реализация товаров и услуг" поле "Склад" находится в табличной части "Товары", и в запросе для печатной формы оно было закомментировано.

Исходный запрос для получения товаров выглядит следующим образом (упрощенно, с акцентом на закомментированные строки, относящиеся к складу):


Запрос.Текст = "ВЫБРАТЬ
    | РеализацияТоваровУслуг.Номер,
    | РеализацияТоваровУслуг.Дата,
    ...
    | РеализацияТоваровУслуг.Товары.(
    | ЦенаСоСкидкой,
    | Склад
    | )
    |ИЗ
    | Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
    |ГДЕ
    | РеализацияТоваровУслуг.Ссылка = &ТекущийДокумент";
...
Запрос.Текст = "ВЫБРАТЬ
    | ВложенныйЗапросПоТоварам.Номенклатура,
    | ВЫРАЗИТЬ(ВложенныйЗапросПоТоварам.Номенклатура.НаименованиеПолное КАК СТРОКА(1000)) КАК Товар,
    ...
    | ВложенныйЗапросПоТоварам.НомерГТД КАК НомерГТД,
    // | ВложенныйЗапросПоТоварам.Склад,
    | ВложенныйЗапросПоТоварам.СерияНоменклатуры КАК Серия,
    ...
    | ИЗ
    | (ВЫБРАТЬ
    | РеализацияТоваровУслуг.Номенклатура КАК Номенлатура,
    ...
    // | РеализацияТоваровУслуг.Склад КАК Склад,
    | РеализацияТоваровУслуг.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
    ...
    | СГРУППИРОВАТЬ ПО
    | РеализацияТоваровУслуг.Номенклатура,
    ...
    | РеализацияТоваровУслуг.СерияНоменклатуры.СтранаПроисхождения) КАК ВложенныйЗапросПоТоварам
    ...
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    | " + СтрокаВыборкиПоляСодержания + ",
    ...
    // | РеализацияТоваровУслуг.Склад,
    | NULL,
    ...
    |ОБЪЕДИНИТЬ ВСЕ
    ...
    |УПОРЯДОЧИТЬ ПО
    | Метка,
    | НомерСтроки";

Из приведенного фрагмента видно, что поле Склад действительно закомментировано в нескольких местах запроса, который выбирает данные для печатной формы. Это означает, что оно просто не попадает в итоговую выборку ЗапросТовары.

Шаг 1: Модификация запроса для включения склада

Первое, что нам необходимо сделать – это раскомментировать поле Склад и добавить его во все необходимые секции запроса. Поскольку запрос использует вложенные запросы и оператор ОБЪЕДИНИТЬ ВСЕ, мы должны быть очень внимательны к согласованности полей.

  1. В основном запросе для таблицы "Товары":

    Раскомментируем выбор поля Склад из вложенного запроса ВложенныйЗапросПоТоварам:

    
    Запрос.Текст = "ВЫБРАТЬ
        ...
        | ВложенныйЗапросПоТоварам.НомерГТД КАК НомерГТД,
        | ВложенныйЗапросПоТоварам.Склад, // Эта строка была закомментирована
        | ВложенныйЗапросПоТоварам.СерияНоменклатуры КАК Серия,
        ...";
    
  2. Во вложенном запросе для таблицы "Товары":

    Здесь также необходимо раскомментировать выбор поля Склад из табличной части документа РеализацияТоваровУслуг.Товары и, что очень важно, добавить его в секцию СГРУППИРОВАТЬ ПО. Если мы не добавим Склад в СГРУППИРОВАТЬ ПО, то получим ошибку "поле не входит в группу или не является агрегатной функцией", поскольку в этом вложенном запросе используются агрегатные функции (СУММА, МИНИМУМ).

    
    Запрос.Текст = "ВЫБРАТЬ
        ...
        | ИЗ
        | (ВЫБРАТЬ
        | РеализацияТоваровУслуг.Номенклатура КАК Номенклатура,
        ...
        | РеализацияТоваровУслуг.СерияНоменклатуры.СтранаПроисхождения КАК СтранаПроисхождения,
        | РеализацияТоваровУслуг.Склад КАК Склад, // Эта строка была закомментирована
        | РеализацияТоваровУслуг.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
        | РеализацияТоваровУслуг.СерияНоменклатуры КАК СерияНоменклатуры,
        | СУММА(РеализацияТоваровУслуг.Количество) КАК Количество,
        ...
        | СГРУППИРОВАТЬ ПО
        | РеализацияТоваровУслуг.Номенклатура,
        | РеализацияТоваровУслуг.ЕдиницаИзмерения,
        | РеализацияТоваровУслуг.ЕдиницаИзмеренияМест,
        | РеализацияТоваровУслуг.Цена,
        | РеализацияТоваровУслуг.СтавкаНДС,
        | РеализацияТоваровУслуг.ПроцентСкидкиНаценки,
        | РеализацияТоваровУслуг.ПроцентАвтоматическихСкидок,
        | РеализацияТоваровУслуг.СерияНоменклатуры,
        | РеализацияТоваровУслуг.ХарактеристикаНоменклатуры,
        | РеализацияТоваровУслуг.СерияНоменклатуры.НомерГТД,
        | РеализацияТоваровУслуг.СерияНоменклатуры.СтранаПроисхождения,
        | РеализацияТоваровУслуг.Склад) КАК ВложенныйЗапросПоТоварам // Добавили Склад в группировку
        ...";
    
  3. В секциях ОБЪЕДИНИТЬ ВСЕ:

    Оператор ОБЪЕДИНИТЬ ВСЕ требует, чтобы количество и типы выбираемых полей во всех частях объединения совпадали. Табличные части "Услуги" и "ВозвратнаяТара" документа "Реализация товаров и услуг" обычно не имеют поля "Склад". Поэтому для этих частей запроса мы должны добавить поле, возвращающее NULL, чтобы сохранить структуру:

    
    ...
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    | " + СтрокаВыборкиПоляСодержания + ",
    ...
    | РеализацияТоваровУслуг.Номенклатура.ЕдиницаХраненияОстатков.Представление,
    | NULL КАК Склад, // Добавили NULL для согласованности с частью Товары
    | NULL,
    ...
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Услуги КАК РеализацияТоваровУслуг
    ...
    |ОБЪЕДИНИТЬ ВСЕ
    |
    |ВЫБРАТЬ
    | РеализацияТоваровУслуг.Номенклатура,
    ...
    | РеализацияТоваровУслуг.Номенклатура.ЕдиницаХраненияОстатков.Представление,
    | NULL КАК Склад, // Добавили NULL для согласованности с частью Товары
    | NULL,
    ...
    |ИЗ
    |(ВЫБРАТЬ
    ...
    
  4. Разрешение ошибки "упорядочить по и итоги должны находиться в списке выбора":

    Эта ошибка, упомянутая в сообщении 15, возникает, когда поле, используемое в СГРУППИРОВАТЬ ПО или УПОРЯДОЧИТЬ ПО, не включено в список полей ВЫБРАТЬ или не является агрегатной функцией. Убедитесь, что все поля, по которым вы группируете или упорядочиваете, присутствуют в списке ВЫБРАТЬ вашего запроса (как мы сделали для Склад).

После выполнения этих изменений, результат запроса ЗапросТовары будет содержать колонку Склад, и мы сможем использовать ее в цикле вывода печатной формы.

Шаг 2: Модификация макета печатной формы

Теперь, когда данные о складах доступны в ЗапросТовары, нам необходимо добавить соответствующую колонку в макет печатной формы "Накладная1" и заполнить ее.

  1. Открытие и редактирование макета:

    Откройте конфигуратор, найдите документ "Реализация товаров и услуг" и его макет "Накладная1". Скорее всего, это макет типа "ТабличныйДокумент".

    Добавьте новую колонку в табличную часть, например, после колонки "Сумма". Присвойте ей имя области, например, "Склад". Убедитесь, что эта область существует как в строке заголовка таблицы, так и в строке с данными.

    В области заголовка таблицы (например, ШапкаТаблицы|Склад) укажите текст "Склад".

    В области данных строки (например, Строка|Склад) создайте параметр с именем, например, СКЛ.

  2. Модификация кода вывода в цикле:

    В коде функции ПечатьДокумента(), внутри цикла Для каждого ВыборкаСтрокТовары из ЗапросТовары Цикл, найдите блок, отвечающий за вывод данных строки. После вывода колонки "Сумма" или в нужном вам месте, добавьте код для вывода области "Склад":

    
    ...
    ОбластьСуммы.Параметры.НомерГТДСтранаПроисхождения = ?(ЗначениеЗаполнено(ВыборкаСтрокТовары.НомерГТД), СокрЛП(ВыборкаСтрокТовары.НомерГТД.Код)+",", "") + Символы.ПС + ВыборкаСтрокТовары.СтранаПроисхождения;
    ТабДокумент.Присоединить(ОбластьСуммы);
    
    // Код для вывода склада
    ОбластьСклад = Макет.ПолучитьОбласть("Строка|Склад"); // Получаем область для вывода склада
    ОбластьСклад.Параметры.Заполнить(ВыборкаСтрокТовары); // Заполняем все параметры из выборки (если они совпадают по имени)
    ОбластьСклад.Параметры.СКЛ = ВыборкаСтрокТовары.Склад; // Явно указываем значение для нашего параметра СКЛ
    ТабДокумент.Присоединить(ОбластьСклад);
    ...
    
  3. Модификация заголовка и итоговой строки:

    Аналогично, необходимо настроить вывод заголовка колонки "Склад" и пустой ячейки в итоговой строке. Найдите соответствующие блоки кода:

    
    // Блок для вывода заголовка таблицы
    ...
    ОбластьСуммы = Макет.ПолучитьОбласть("ШапкаТаблицы|Сумма");
    ОбластьСклад = Макет.ПолучитьОбласть("ШапкаТаблицы|Склад"); // Получаем область заголовка склада
    ТабДокумент.Вывести(ОбластьНомера);
    Если ВыводитьКоды Тогда
        ОбластьКодов.Параметры.ИмяКолонкиКодов = Колонка;
        ТабДокумент.Присоединить(ОбластьКодов);
    КонецЕсли;
    ТабДокумент.Присоединить(ОбластьТовар);
    Если флВыводитьМест Тогда
        ТабДокумент.Присоединить(ОбластьМест);
    КонецЕсли;
    ТабДокумент.Присоединить(ОбластьДанных);
    Если ЕстьСкидки Тогда
        ТабДокумент.Присоединить(ОбластьСкидок);
    КонецЕсли;
    ТабДокумент.Присоединить(ОбластьСуммы);
    ТабДокумент.Присоединить(ОбластьСклад); // Присоединяем заголовок склада
    ...
    // Блок для вывода итоговой строки
    ...
    ОбластьСуммы = Макет.ПолучитьОбласть("Итого|Сумма");
    ОбластьСклад = Макет.ПолучитьОбласть("Итого|Склад"); // Получаем область для итоговой строки склада
    ТабДокумент.Вывести(ОбластьНомера);
    Если ВыводитьКоды Тогда
        ТабДокумент.Присоединить(ОбластьКодов);
    КонецЕсли;
    ТабДокумент.Присоединить(ОбластьТовар);
    Если флВыводитьМест Тогда
        ТабДокумент.Присоединить(ОбластьМест);
    КонецЕсли;
    ТабДокумент.Присоединить(ОбластьДанных);
    Если ЕстьСкидки Тогда
        ОбластьСкидок.Параметры.ВсегоСкидок = ВсегоСкидок;
        ОбластьСкидок.Параметры.ВсегоБезСкидок = ВсегоСкидок+СуммаДокумента;
        ТабДокумент.Присоединить(ОбластьСкидок);
    КонецЕсли;
    ОбластьСуммы.Параметры.Всего = ОбщегоНазначения.ФорматСумм(Сумма);
    ТабДокумент.Присоединить(ОбластьСуммы);
    ТабДокумент.Присоединить(ОбластьСклад); // Присоединяем пустую ячейку для склада в итогах
    ...
    

С этими изменениями каждая строка в печатной форме, соответствующая товару, будет отображать свой склад. Если в исходном документе была номенклатура "Товар А" со "Склада 1" и "Товар А" со "Склада 2" (и все остальные группировочные поля отличались или совпадали), то в печатной форме они появятся как отдельные строки, каждая со своим складом.

Шаг 3: Объединение складов в одну строку (для продвинутых сценариев)

Если ваша задача состоит в том, чтобы для одной и той же номенклатуры (даже если она встречается на разных складах в разных строках документа) вывести одну строку в печатной форме, а склады перечислить через запятую, то это требует дополнительной логики. Запрос 1С не имеет встроенной функции типа GROUP_CONCAT из SQL. Поэтому нам потребуется программная обработка после выполнения запроса.

Один из вариантов решения был предложен в сообщении 7 с функцией ЯНеХочуУчится. Однако она содержала ошибку: метод СкопироватьКолонки не может быть вызван напрямую для табличной части ЭтотОбъект.Товары. Сначала табличную часть нужно выгрузить в таблицу значений.

Рассмотрим, как можно реализовать такую функцию:

Корректировка функции ЯНеХочуУчится

Исходная задумка функции ЯНеХочуУчится(Таб,мНоменклатура) заключалась в том, чтобы взять табличную часть документа, найти все строки по заданной номенклатуре и собрать все уникальные склады в одну строку через запятую. Для корректной работы ее необходимо немного модифицировать:


// В модуле объекта или модуле менеджера, в зависимости от места вызова
// Если вызов происходит из модуля объекта документа, то "ЭтотОбъект.Товары" доступен.

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

    // 3. Сворачиваем таблицу по Номенклатуре и Складу, чтобы получить уникальные пары
    // Это гарантирует, что каждый склад для данной номенклатуры будет учтен только один раз.
    мТаб.Свернуть("Номенклатура, Склад");
    
    // 4. Ищем строки, соответствующие заданной номенклатуре
    Отбор = Новый Структура;
    Отбор.Вставить("Номенклатура", мНоменклатура);
    СтрокиПоНоменклатуре = мТаб.НайтиСтроки(Отбор);
    
    мСкладСтрокой = "";
    Для Каждого мСтрока Из СтрокиПоНоменклатуре Цикл
        Если ЗначениеЗаполнено(мСтрока.Склад) Тогда
            Если НЕ ПустаяСтрока(мСкладСтрокой) Тогда
                мСкладСтрокой = мСкладСтрокой + ", ";
            КонецЕсли;
            мСкладСтрокой = мСкладСтрокой + Строка(мСтрока.Склад);
        КонецЕсли;
    КонецЦикла;
    
    Возврат мСкладСтрокой;
КонецФункции

Пояснения к корректировкам:

  1. Метод СкопироватьКолонки() (если бы он был доступен для коллекции строк) или ВыгрузитьКолонки() (доступен для табличной части) возвращает новую таблицу значений с указанными колонками. Мы используем ВыгрузитьКолонки() на самой табличной части, что является более прямым путем.
  2. Метод Свернуть() используется для удаления дублирующихся строк по указанным полям, что важно для получения уникального списка складов.
  3. Цикл по найденным строкам собирает имена складов, разделяя их запятыми.

Применение функции ПолучитьСкладыСтрокойДляНоменклатуры

Чтобы использовать эту функцию, вы должны принять решение: вы хотите, чтобы в печатной форме для каждой уникальной комбинации (Номенклатура, Характеристика, Цена, Склад) была своя строка (что достигается Шагом 1), или же вы хотите агрегировать склады для каждой уникальной комбинации (Номенклатура, Характеристика, Цена) и показать их все в одной строке.

Сценарий 1: Каждая строка запроса – это уникальный товар со своим складом.

Если вы хотите выводить Склад для каждой строки, как она получена из запроса (т.е. если ЗапросТовары уже содержит Склад в группировке и каждая строка ВыборкаСтрокТовары соответствует уникальному складу), то вам достаточно просто использовать ВыборкаСтрокТовары.Склад, как было описано в Шаге 2.


// Внутри цикла Для каждого ВыборкаСтрокТовары из ЗапросТовары Цикл
...
ОбластьСклад.Параметры.СКЛ = ВыборкаСтрокТовары.Склад;
ТабДокумент.Присоединить(ОбластьСклад);
...

Сценарий 2: Агрегация складов для одной номенклатуры.

Если вы хотите, чтобы для одной номенклатуры (независимо от того, на скольких складах она числится в документе) была выведена одна строка в печатной форме, а все склады были перечислены через запятую, вам потребуется изменить логику запроса, чтобы он не группировал по Склад (или группировал на более высоком уровне), а затем вызывать функцию ПолучитьСкладыСтрокойДляНоменклатуры для каждой строки. Это сложнее, поскольку текущий запрос сильно группирует по множеству полей.

Для этого вам придется:

  1. Убрать Склад из секции СГРУППИРОВАТЬ ПО во вложенном запросе для товаров.
  2. Вместо прямого получения ВложенныйЗапросПоТоварам.Склад в основном запросе, вызывать функцию ПолучитьСкладыСтрокойДляНоменклатуры.

Пример применения функции:


// Внутри цикла Для каждого ВыборкаСтрокТовары из ЗапросТовары Цикл
...
// Предполагаем, что Склад был удален из группировки в запросе,
// и теперь ВыборкаСтрокТовары.Номенклатура является более агрегированной.
СтрокаСкладов = ПолучитьСкладыСтрокойДляНоменклатуры(ЭтотОбъект.Товары, ВыборкаСтрокТовары.Номенклатура);
ОбластьСклад = Макет.ПолучитьОбласть("Строка|Склад");
ОбластьСклад.Параметры.СКЛ = СтрокаСкладов;
ТабДокумент.Присоединить(ОбластьСклад);
...

Этот подход более трудоемок, так как требует значительного пересмотра логики группировки в запросе, чтобы одна номенклатура, имеющая разные склады, не разбивалась на несколько строк в ЗапросТовары по другим параметрам (характеристика, цена, серия). Если вам нужна именно такая агрегация, убедитесь, что ваш запрос собирает все данные по номенклатуре в одну строку, прежде чем применять эту функцию для сбора списка складов.

Резюме и рекомендации

Наиболее простой и часто используемый способ решения вашей задачи – это корректно включить поле Склад в запрос и добавить соответствующую колонку в макет. Это позволит каждой строке товара в печатной форме явно указывать свой склад, если он был указан в документе.

Если же требуется более сложная агрегация, где для одной номенклатуры, отгруженной с нескольких складов, нужна одна строка с перечислением всех складов, то вам придется глубже переработать логику запроса, чтобы избежать группировки по Склад и другим детальным полям, и затем использовать программную функцию для объединения данных о складах.

Всегда помните о необходимости тщательной проверки соответствия количества и типов полей при использовании оператора ОБЪЕДИНИТЬ ВСЕ в запросах 1С. Это одно из самых распространенных мест для ошибок при модификации стандартных запросов.

← На главную