При разработке отчетов в системе «1С:Предприятие 8.3» программисты часто сталкиваются с нестандартными требованиями бизнеса. Одно из таких требований — организовать детальный учет остатков товаров или материалов на складах в разрезе номенклатурных групп, привязанных к конкретным документам поступления или перемещения (например, под определенные проекты или договоры). Попытка решить эту задачу «в лоб» через соединение виртуальных таблиц остатков регистра бухгалтерии с табличными частями документов в схеме компоновки данных (СКД) часто приводит к непредсказуемому результату: при изменении настроек группировки отчет начинает двоить строки, выводить пустые значения или привязывать к документам чужую номенклатуру. Разберем подробно, почему так происходит, как работает механизм компоновки данных в таких сценариях и какие архитектурные подходы позволят решить задачу правильно.
Рассмотрим ситуацию, когда разработчик пытается связать данные виртуальной таблицы РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты с табличной частью Документ.ПоступлениеТоваровУслуг.Товары. В исходном запросе автора связь устанавливается по следующему условию:
ХозрасчетныйОстаткиИОбороты.Субконто1 = ПоступлениеТоваровУслугТовары.Номенклатура
И ХозрасчетныйОстаткиИОбороты.Регистратор = ПоступлениеТоваровУслугТовары.Ссылка
И ХозрасчетныйОстаткиИОбороты.Субконто2 = ПоступлениеТоваровУслугТовары.Ссылка.Склад
И (ВЫБОР
КОГДА ХозрасчетныйОстаткиИОбороты.Регистратор В (ВЫБРАТЬ РАЗЛИЧНЫЕ Регистратор ИЗ номерарег)
ТОГДА ХозрасчетныйОстаткиИОбороты.НомерСтроки = ПоступлениеТоваровУслугТовары.НомерСтроки + 1
ИНАЧЕ ХозрасчетныйОстаткиИОбороты.НомерСтроки = ПоступлениеТоваровУслугТовары.НомерСтроки
КОНЕЦ)
В этой конструкции скрыто сразу несколько серьезных уязвимостей, которые неизбежно приводят к неверным данным в отчете:
НомерСтроки в регистре бухгалтерии Хозрасчетный — это порядковый номер проводки в наборе записей по конкретному регистратору. Поле НомерСтроки в табличной части Товары документа — это индекс строки в документе. Они принципиально не обязаны совпадать. При проведении документа программа может группировать одинаковые позиции номенклатуры в одну проводку (сворачивать их для оптимизации), генерировать дополнительные проводки по зачету авансов, НДС или отклонениям стоимости. Как только баланс строк нарушается, жесткое условие соединения по номеру строки (даже со смещением + 1) перестает работать, и данные «съезжают».ОстаткиИОбороты возвращает сгруппированные данные по периодам и субконто. В ней нет физического поля НомерСтроки, аналогичного физической таблице движений регистра. Использование поля НомерСтроки в контексте этой виртуальной таблицы (если СКД не разворачивает запрос до детальных записей) приводит к непредсказуемому поведению СУБД при формировании итогового SQL-запроса.Хозрасчетный.Содержание = "Зачет аванса поставщику" — это крайне хрупкое решение. Текст содержания может измениться при обновлении конфигурации, ручном редактировании операции бухгалтером или локализации базы данных. Любое изменение лишит алгоритм возможности сопоставлять данные.Выясним причину, по которой при одной структуре отчета (например, «Документ» вверху, «Номенклатура» внизу) данные отображаются корректно, а при обратной структуре («Номенклатура» вверху, «Документ» внизу) появляется чужая номенклатура. Дело в механизме оптимизации запросов СКД.
Компоновщик макета СКД анализирует только те поля, которые в данный момент выводятся в отчет или используются в группировках и отборах. Если в структуре отчета группировка по регистратору стоит выше группировки по номенклатуре, СКД строит дерево запроса последовательно. Но как только мы меняем группировки местами, СКД перестраивает внутренний запрос к СУБД. При включенном свойстве Автозаполнение компоновщик может:
номерарег, если посчитает, что данные из нее не влияют на корневую группировку.Для детального анализа поведения СКД всегда рекомендуется использовать Консоль компоновки данных. С ее помощью можно выгрузить реальный SQL-запрос, который СКД отправляет на сервер СУБД в каждом из вариантов настроек, и наглядно увидеть, в каком месте пропадает связь между таблицами.
Пытаться получить остатки по разрезу, которого физически нет в регистре накопления или регистре бухгалтерии, путем динамического соединения с документами — это тупиковый путь. Документ фиксирует лишь факт хозяйственной операции в определенный момент времени. Он не хранит остаток. На складе лежат абстрактные «10 болтов», и система не знает, из какого именно поступления они остались, если не ведется партионный учет.
Проанализируем три практических способа реализации этой задачи.
Если вносить изменения в структуру метаданных конфигурации категорически нельзя, необходимо сделать запрос в СКД максимально строгим и отказаться от нестабильных связей по номерам строк. Нам нужно связать номенклатурную группу из документа поступления с движениями по регистру. Для этого перепишем логику соединения.
Вместо использования виртуальной таблицы ОстаткиИОбороты вместе с детальными данными документов, разделим получение оборотов (которые жестко привязаны к регистратору) и получение остатков (которые распределяются по субконто). Пример более безопасного соединения таблиц в запросе:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ХозрасчетныйДвижения.Регистратор КАК Регистратор,
ХозрасчетныйДвижения.Период КАК Период,
ХозрасчетныйДвижения.Субконто1 КАК Номенклатура,
ХозрасчетныйДвижения.Субконто2 КАК Склад,
ХозрасчетныйДвижения.КоличествоОборотДт КАК КоличествоПоступление,
ПоступлениеТовары.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа
ПОМЕСТИТЬ ВТ_ДвиженияПоступления
ИЗ
РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(&НачалоПериода, &КонецПериода, Запись, Счет В ИЕРАРХИИ (&МассивСчетов), &ВидыСубконтоНомСкл, , , ) КАК ХозрасчетныйДвижения
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТовары
ПО ХозрасчетныйДвижения.Регистратор = ПоступлениеТовары.Ссылка
И ХозрасчетныйДвижения.Субконто1 = ПоступлениеТовары.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ДвиженияПоступления.Номенклатура КАК Номенклатура,
ВТ_ДвиженияПоступления.Склад КАК Склад,
ВТ_ДвиженияПоступления.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
ВТ_ДвиженияПоступления.Регистратор КАК Регистратор,
ВТ_ДвиженияПоступления.КоличествоПоступление КАК Количество
ИЗ
ВТ_ДвиженияПоступления КАК ВТ_ДвиженияПоступления
Важное правило форматирования СКД: Чтобы избежать дублирования остатков, необходимо зайти на вкладку Наборы данных, выбрать полученное поле НоменклатурнаяГруппа и в колонке Роль указать связь с субконто или измерением, чтобы СКД понимала, как правильно агрегировать ресурсы по этой аналитике.
В типовой конфигурации «1С:Бухгалтерия предприятия 3.0» на счетах 10 «Материалы» и 41 «Товары» по умолчанию используются субконто Номенклатура, Склады и Партии. Если бизнес требует вести полноценный бухгалтерский учет остатков по номенклатурным группам прямо на счетах ТМЦ, то самым правильным решением будет добавление нового субконто на эти счета.
Рассмотрим этот процесс пошагово:
Хозрасчетный в режиме «1С:Предприятие» или через конфигуратор.Партии, если партионный учет отключен) устанавливаем предопределенный вид субконто НоменклатурныеГруппы.При таком подходе виртуальная таблица ОстаткиИОбороты сразу начнет отдавать корректные остатки по номенклатурным группам без каких-либо сложных соединений в запросе отчета. Запрос станет тривиальным, быстрым и надежным.
В сообщении №19 автор упомянул попытку создать регистр накопления в расширении, после чего копия базы данных «упала». Создание новых регистров накопления через расширения в 1С требует строгого соблюдения правил, иначе возможна блокировка таблиц СУБД или повреждение структуры конфигурации.
Разберем, как правильно реализовать этот подход через механизм расширений, избежав критических ошибок:
УчетьТМЦПоПроектам. Регистрами оборотов пользоваться в данном случае нельзя, так как они не умеют рассчитывать остатки на произвольную дату.Номенклатура (тип СправочникСсылка.Номенклатура), Склад (тип СправочникСсылка.Склады), НоменклатурнаяГруппа (тип СправочникСсылка.НоменклатурныеГруппы).Количество (тип Число, 15 знаков, точность 3), Сумма (тип Число, 15 знаков, точность 2).ПоступлениеТоваровУслуг, ПеремещениеТоваров, РасходныйОрдерНаТовары и т.д.) в состав регистраторов нового регистра накопления.ОбработкаПроведения в расширении для каждого документа по схеме После или Вместо:
&После("ОбработкаПроведения")
Процедура Расш1_ОбработкаПроведенияПосле(Отказ, РежимПроведения)
// Инициализируем набор записей нашего регистра
ДвиженияРегистра = Движения.Расш1_УчетьТМЦПоПроектам;
// Заполняем движения на основании данных табличной части документа
Для Каждого СтрокаТовара Из Товары Цикл
Движение = ДвиженияРегистра.ДобавитьРасход(); // Или ДобавитьПриход() для поступления
Движение.Период = Дата;
Движение.Номенклатура = СтрокаТовара.Номенклатура;
Движение.Склад = Склад; // Для перемещений учитываем склад-отправитель и склад-получатель
Движение.НоменклатурнаяГруппа = СтрокаТовара.НоменклатурнаяГруппа;
Движение.Количество = СтрокаТовара.Количество;
КонецЦикла;
// Записываем движения в базу данных
ДвиженияРегистра.Записать();
КонецПроцедуры
Этот способ гарантирует максимальное быстродействие системы. База данных не будет падать, так как расширение аккуратно дополняет транзакцию проведения документа, не вступая в конфликт с блокировками платформы 1С. Отчет в СКД в этом случае будет строиться по простой виртуальной таблице РегистрНакопления.Расш1_УчетьТМЦПоПроектам.ОстаткиИОбороты, что исключает любые нестыковки данных при любых настройках группировок пользователями.
Для создания стабильного и масштабируемого отчета по остаткам номенклатуры в разрезе кастомных аналитик (таких как номенклатурные группы документов) следует придерживаться следующих правил:
НомерСтроки — это нарушает базовую логику СУБД и приводит к дублированию строк.