Как разработать отчет по остаткам, поступлению и реализации ТМЦ без учета перемещений в 1С

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

При разработке отчетов по складским остаткам и оборотам в программе 1С:Бухгалтерия для Казахстана (редакция 2.0) начинающие программисты часто совершают архитектурные ошибки. Типичный пример — попытка связать данные остатков на счетах учета ТМЦ (например, на счете 1330 "Товары") с таблицами документов поступления и реализации напрямую через субконто. Вторая частая проблема — исключение внутренних перемещений между складами, чтобы получить "чистые" закупки и продажи.

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

Разбор главной ошибки: Соединение остатков по субконто

В процессе обсуждения автор темы пытался связать виртуальную таблицу остатков и оборотов с физическими таблицами документов с помощью следующего условия:


ПО ТиповойОстаткиИОбороты.Субконто1 = РеализацияТоваровУслуг.Ссылка
    И ТиповойОстаткиИОбороты.Subkonto2 = РеализацияТоваровУслуг.Ссылка
    И ТиповойОстаткиИОбороты.Субконто3 = РеализацияТоваровУслуг.Ссылка

Давайте выясним причину, почему такой запрос никогда не вернет данные (в полях "Поступило" и "Продано" всегда будет значение NULL). Для этого проанализируем структуру плана счетов. На счете 1330 настроены три вида субконто:

  1. Субконто 1: Номенклатура (тип данных — СправочникСсылка.Номенклатура).
  2. Субконто 2: Склады (тип данных — СправочникСсылка.Склады).
  3. Субконто 3: Партии (если ведется партионный учет, обычно это документ поступления, но точно не реализации).

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

Как правильно исключить перемещения (внутренние обороты)

Внутреннее перемещение товаров со склада на склад отражается в бухгалтерском учете проводкой: Дебет 1330 (Склад-получатель) — Кредит 1330 (Склад-отправитель).

Если мы просто возьмем обороты по дебету и кредиту счета 1330, то перемещения увеличат показатели и прихода, и расхода, исказив реальную картину продаж и закупок. Чтобы получить "чистые" поступления от поставщиков и "чистые" продажи покупателям, нам нужно наложить условие на корреспондирующие счета (КорСчет):

Поскольку в виртуальной таблице ОстаткиИОбороты нет поля КорСчет (так как остатки не привязаны к двойной записи конкретных проводок), нам потребуется объединить две разные виртуальные таблицы регистра бухгалтерии: таблицу ОстаткиИОбороты (для получения остатков на начало и конец периода) и таблицу Обороты (для получения чистых оборотов без учета счета 1330 в корреспонденции).

Проектирование оптимального запроса

Рассмотрим структуру правильного запроса. Мы будем использовать пакетные запросы и временные таблицы для повышения производительности и читаемости кода.


ВЫБРАТЬ
    Остатки.Субконто1 КАК Номенклатура,
    Остатки.Субконто2 КАК Склад,
    Остатки.КоличествоНачальныйОстаток КАК НачОстаток,
    Остатки.КоличествоКонечныйОстаток КАК КонОстаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
    РегистрБухгалтерии.Типовой.ОстаткиИОбороты(
        &НачДата, 
        &КонДата, 
        Период, 
        , 
        Счет = &СчетУчета, 
        &ВидыСубконто, 
        Субконто2 = &Склад И Субконто1 В ИЕРАРХИИ(&ГруппаТМЦ)
    ) КАК Остатки
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Обороты.Субконто1 КАК Номенклатура,
    Обороты.Субконто2 КАК Склад,
    Обороты.КоличествоОборотДт КАК Поступило,
    Обороты.КоличествоОборотКт КАК Продано
ПОМЕСТИТЬ ВТ_Обороты
ИЗ
    РегистрБухгалтерии.Типовой.Обороты(
        &НачДата, 
        &КонДата, 
        Период, 
        Счет = &СчетУчета, 
        &ВидыСубконто, 
        Субконто2 = &Склад И Субконто1 В ИЕРАРХИИ(&ГруппаТМЦ), 
        НЕ КорСчет В ИЕРАРХИИ (&СчетУчета), 
    ) КАК Обороты
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЕСТЬNULL(ВТ_Остатки.Номенклатура, ВТ_Обороты.Номенклатура) КАК Номенклатура,
    ЕСТЬNULL(ВТ_Остатки.Склад, ВТ_Обороты.Склад) КАК Склад,
    ЕСТЬNULL(ВТ_Остатки.НачОстаток, 0) КАК НачОстаток,
    ЕСТЬNULL(ВТ_Остатки.КонОстаток, 0) КАК КонОстаток,
    ЕСТЬNULL(ВТ_Обороты.Поступило, 0) КАК Поступило,
    ЕСТЬNULL(ВТ_Обороты.Продано, 0) КАК Продано
ИЗ
    ВТ_Остатки КАК ВТ_Остатки
        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ВТ_Обороты
        ПО ВТ_Остатки.Номенклатура = ВТ_Обороты.Номенклатура
            И ВТ_Остатки.Склад = ВТ_Обороты.Склад

Проанализируем ключевые моменты этого запроса:

Реализация отчета без СКД (на обычных формах)

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

Перед написанием кода убедитесь, что в макете вашего отчета созданы три области строк:

  1. Шапка: заголовок отчета и шапка таблицы (колонки: Номенклатура, Остаток на начало, Поступило, Продано, Остаток на конец).
  2. Строка: строка с параметрами для заполнения данных по каждой номенклатуре.
  3. Подвал: строка с итогами по количеству.

Теперь разберем программный код формирования табличного документа:


Процедура ДействиеСформировать(Кнопка)
    
    // 1. Инициализация табличного документа и очистка прошлых результатов
    ТабДок = ЭлементыФормы.ТабличныйДокументРезультат;
    ТабДок.Очистить();
    
    Макет = ПолучитьМакет("Макет");
    
    // Вывод области "Шапка"
    ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
    ОбластьШапка.Параметры.НачДата = НачДата;
    ОбластьШапка.Параметры.КонДата = КонДата;
    ОбластьШапка.Параметры.Склад = Склад;
    ТабДок.Вывести(ОбластьШапка);
    
    // 2. Создание и настройка запроса
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Остатки.Субконто1 КАК Номенклатура,
    |    Остатки.Субконто2 КАК Склад,
    |    Остатки.КоличествоНачальныйОстаток КАК НачОстаток,
    |    Остатки.КоличествоКонечныйОстаток КАК КонОстаток
    |ПОМЕСТИТЬ ВТ_Остатки
    |ИЗ
    |    РегистрБухгалтерии.Типовой.ОстаткиИОбороты(&НачДата, &КонДата, , , Счет = &СчетУчета, &ВидыСубконто, Субконто2 = &Склад И Субконто1 В ИЕРАРХИИ(&ГруппаТМЦ)) КАК Остатки
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    Обороты.Субконто1 КАК Номенклатура,
    |    Обороты.Субконто2 КАК Склад,
    |    Обороты.КоличествоОборотДт КАК Поступило,
    |    Обороты.КоличествоОборотКт КАК Продано
    |ПОМЕСТИТЬ ВТ_Обороты
    |ИЗ
    |    РегистрБухгалтерии.Типовой.Обороты(&НачДата, &КонДата, , Счет = &СчетУчета, &ВидыСубконто, Субконто2 = &Склад И Субконто1 В ИЕРАРХИИ(&ГруппаТМЦ), НЕ КорСчет В ИЕРАРХИИ (&СчетУчета), ) КАК Обороты
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ЕСТЬNULL(ВТ_Остатки.Номенклатура, ВТ_Обороты.Номенклатура) КАК Номенклатура,
    |    ЕСТЬNULL(ВТ_Остатки.НачОстаток, 0) КАК НачОстаток,
    |    ЕСТЬNULL(ВТ_Остатки.КонОстаток, 0) КАК КонОстаток,
    |    ЕСТЬNULL(ВТ_Обороты.Поступило, 0) КАК Поступило,
    |    ЕСТЬNULL(ВТ_Обороты.Продано, 0) КАК Продано
    |ИЗ
    |    ВТ_Остатки КАК ВТ_Остатки
    |        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ВТ_Обороты
    |        ПО ВТ_Остатки.Номенклатура = ВТ_Обороты.Номенклатура
    |            И ВТ_Остатки.Склад = ВТ_Обороты.Склад
    |УПОРЯДОЧИТЬ ПО
    |    Номенклатура.Наименование";
    
    // 3. Подготовка параметров запроса
    // Формируем массив видов субконто для правильного сопоставления индексов
    ВидыСубконто = Новый Массив;
    ВидыСубконто.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура);
    ВидыСубконто.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Склады);
    
    // Передаем параметры из реквизитов формы отчета
    Запрос.УстановитьПараметр("НачДата", НачалоДня(НачДата));
    // Конец дня важен, чтобы захватить движения, сделанные в последнюю секунду периода
    Запрос.УстановитьПараметр("КонДата", КонецДня(КонДата));
    Запрос.УстановитьПараметр("Склад", Склад);
    Запрос.УстановитьПараметр("ГруппаТМЦ", ГруппаТМЦ);
    Запрос.УстановитьПараметр("СчетУчета", ПланыСчетов.Типовой.НайтиПоКоду("1330"));
    Запрос.УстановитьПараметр("ВидыСубконто", ВидыСубконто);
    
    // 4. Обход результатов и вывод в макет
    Выборка = Запрос.Выполнить().Выбрать();
    
    ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
    
    ИтогНачОстаток = 0;
    ИтогПоступило  = 0;
    ИтогПродано    = 0;
    ИтогКонОстаток = 0;
    
    Пока Выборка.Следующий() Цикл
        
        // Заполняем параметры текущей строки
        ОбластьСтрока.Параметры.Номенклатура = Выборка.Номенклатура;
        ОбластьСтрока.Параметры.НачОстаток   = Выборка.НачОстаток;
        ОбластьСтрока.Параметры.Поступило     = Выборка.Поступило;
        ОбластьСтрока.Параметры.Продано       = Выборка.Продано;
        ОбластьСтрока.Параметры.КонОстаток   = Выборка.КонОстаток;
        
        // Выводим строку в табличный документ
        ТабДок.Вывести(ОбластьСтрока);
        
        // Накапливаем итоги по колонкам
        ИтогНачОстаток = ИтогНачОстаток + Выборка.НачОстаток;
        ИтогПоступило  = ИтогПоступило  + Выборка.Поступило;
        ИтогПродано    = ИтогПродано    + Выборка.Продано;
        ИтогКонОстаток = ИтогКонОстаток + Выборка.КонОстаток;
        
    КонецЦикла;
    
    // Вывод области "Подвал" с подсчетом итогов
    ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
    ОбластьПодвал.Параметры.ИтогНачОстаток = ИтогНачОстаток;
    ОбластьПодвал.Параметры.ИтогПоступило  = ИтогПоступило;
    ОбластьПодвал.Параметры.ИтогПродано    = ИтогПродано;
    ОбластьПодвал.Параметры.ИтогКонОстаток = ИтогКонОстаток;
    ТабДок.Вывести(ОбластьПодвал);
    
    // Запрещаем редактирование сформированного документа пользователем
    ТабДок.ТолькоПросмотр = Истина;
    
КонецПроцедуры

Альтернативный вариант: Использование Системы компоновки данных (СКД)

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

Для создания отчета в СКД выполним следующие шаги:

  1. Создаем новый отчет, нажимаем кнопку "Открыть схему компоновки данных".
  2. Добавляем набор данных - запрос и вставляем наш разработанный пакетный запрос.
  3. Переходим на вкладку "Параметры". Для параметров &НачДата и &КонДата настраиваем выражение приведения к началу и концу дня соответственно: НачалоПериода(&НачДата, "День") и КонецПериода(&КонДата, "День").
  4. На вкладке "Ресурсы" выбираем поля НачОстаток, Поступило, Продано, КонОстаток и указываем для них выражение расчета итогов: Сумма(...). Это позволит СКД автоматически рассчитывать суммы по группировкам складов и иерархии номенклатуры.
  5. На вкладке "Настройки" создаем структуру отчета. Добавляем группировку по полю Склад, а внутрь нее — группировку по полю Номенклатура (в свойствах группировки номенклатуры можно выбрать тип "Иерархия", чтобы красиво отобразить группы справочника ТМЦ).
  6. В выбранных полях настроек указываем наши числовые ресурсы и наименование номенклатуры.

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

← На главную