Когда мы сталкиваемся с необходимостью построения иерархических отчетов в системе 1С, часто возникает задача отображения данных, структура которых не соответствует стандартным иерархическим справочникам. В таких случаях мы можем использовать произвольные данные, например, хранящиеся в регистре сведений. Именно такая ситуация возникла у нашего коллеги, который пытается построить иерархию по сериям номенклатуры, используя регистр сведений с измерениями Серия, Номенклатура, СерияРодитель и НоменклатураРодитель. Основная проблема, с которой мы можем столкнуться при этом подходе – это дублирование данных в отчете СКД. В этой статье мы подробно разберем, как эффективно реализовать такую иерархию и как бороться с возникающими дублями.
Давайте начнем с фундаментальных принципов построения иерархии по произвольным данным, таким как записи регистра сведений. В отличие от иерархических справочников, где структура "родитель-потомок" уже встроена, регистр сведений требует от нас явного указания этих связей. Для этого мы используем измерения Серия (как элемент-потомок) и СерияРодитель (как элемент-родитель).
Для создания иерархического отчета в Системе Компоновки Данных (СКД) по произвольным данным регистра сведений, нам потребуется определить, как наша иерархическая структура будет представлена. Обычно мы можем использовать один из двух подходов:
Один набор данных с самосоединением: Мы можем использовать один набор данных, который будет содержать все необходимые измерения и ресурсы из нашего регистра сведений. Для построения иерархии нам может потребоваться выполнить самосоединение этого набора данных в запросе, чтобы определить связи "родитель-потомок". Этот метод подходит, когда иерархия неглубокая или когда мы хотим максимально упростить схему.
Предположим, наш регистр сведений называется НашРегистрСведенийПоСериям. Нам нужно будет выбрать поля Серия, Номенклатура, СерияРодитель, НоменклатураРодитель, а также любые ресурсные поля, например, Количество или Сумма, если они есть. Пример запроса для основного набора данных может выглядеть так:
ВЫБРАТЬ
НашРегистрСведенийПоСериям.Серия КАК Серия,
НашРегистрСведенийПоСериям.Номенклатура КАК Номенклатура,
НашРегистрСведенийПоСериям.СерияРодитель КАК СерияРодитель,
НашРегистрСведенийПоСериям.НоменклатураРодитель КАК НоменклатураРодитель,
НашРегистрСведенийПоСериям.Количество КАК Количество,
НашРегистрСведенийПоСериям.Сумма КАК Сумма
ИЗ
РегистрСведений.НашРегистрСведенийПоСериям КАК НашРегистрСведенийПоСериям
В этом запросе мы выбираем исходные данные. Поля Серия и СерияРодитель станут основой для нашей иерархии.
Несколько наборов данных: Альтернативный и часто более гибкий подход заключается в создании нескольких наборов данных. Один набор данных будет содержать основные сведения (например, Серия, Номенклатура, Количество, Сумма), а другой – специально описывать иерархическую структуру, содержащую поля, явно определяющие "Элемент" и "Родитель". В нашем случае это будут Серия и СерияРодитель.
Мы можем создать один набор данных ОсновныеДанные, который выберет все записи из регистра сведений, включая детальные данные и потенциальных родителей. Затем мы можем создать второй, дополнительный набор данных ИерархияСерий, который будет содержать только уникальные пары Серия и СерияРодитель, формируя каркас нашей иерархии. Если поле СерияРодитель не заполнено, это будет означать, что данная серия является элементом верхнего уровня.
Пример запроса для набора данных, определяющего иерархию (например, набора данных "Иерархия"):
ВЫБРАТЬ РАЗЛИЧНЫЕ
НашРегистрСведенийПоСериям.Серия КАК ЭлементИерархии,
НашРегистрСведенийПоСериям.СерияРодитель КАК РодительИерархии
ИЗ
РегистрСведений.НашРегистрСведенийПоСериям КАК НашРегистрСведенийПоСериям
В этом случае, поле ЭлементИерархии будет соответствовать дочернему элементу, а РодительИерархии – его родителю. Ключевое слово РАЗЛИЧНЫЕ здесь помогает избежать дублирования связей, так как одна и та же пара родитель-потомок может встречаться в регистре несколько раз.
Выбор подхода зависит от сложности вашей иерархии и от того, насколько детально вы хотите контролировать каждый уровень. Для начала, мы можем попробовать использовать один набор данных и настроить связи внутри СКД.
После того как мы определили наши наборы данных, ключевым шагом является правильная настройка связей между ними. Если мы используем несколько наборов данных, нам необходимо указать СКД, как они связаны между собой. Это делается в разделе "Связи наборов данных" в схеме компоновки данных.
Для построения иерархии мы устанавливаем связь между полем, которое является дочерним элементом (в нашем случае это Серия из набора ОсновныеДанные или ЭлементИерархии из набора Иерархия), и полем, которое является его родителем (СерияРодитель из ОсновныеДанные или РодительИерархии из Иерархия). Если мы используем один набор данных, связи могут быть определены для группировок, что мы рассмотрим далее.
При использовании двух наборов данных (ОсновныеДанные и Иерархия), связь будет выглядеть примерно так:
Источник связи: Набор данных ОсновныеДанные.
Приемник связи: Набор данных Иерархия.
Выражение связи: Мы можем связать детальные записи с их иерархическими "родителями". Например, если ОсновныеДанные содержит поле Серия, а Иерархия содержит поля ЭлементИерархии и РодительИерархии, то мы можем связать Серия из ОсновныеДанные с ЭлементИерархии из Иерархия. Или, если иерархия строится "снизу вверх", то СерияРодитель из ОсновныеДанные с ЭлементИерархии.
Более типичный подход для иерархии "сверху вниз" - это использование связей внутри самой структуры отчета, где мы указываем поля иерархии при настройке группировок. Это позволяет СКД автоматически строить дерево на основе определенных полей.
Важный момент: убедитесь, что типы данных полей, участвующих в связи, совпадают, иначе связь будет работать некорректно или не будет работать вовсе. Также проверьте, что поля однозначно идентифицируют отношения "родитель-потомок".
После того как мы подготовили наборы данных и, если необходимо, настроили связи, переходим к самому отчету. В настройках отчета (в структуре) нам нужно правильно определить группировки, чтобы СКД смогла отобразить иерархию. Разберем этот процесс по шагам:
Добавление основной группировки: Первым делом мы добавляем группировку по полю, которое является родителем верхнего уровня. В нашем случае это может быть поле СерияРодитель, но чаще всего для построения иерархии мы создаем группировку по самому элементу Серия, а затем указываем для него поле, которое является его родителем.
Указание типа иерархии: Для созданной группировки (например, по полю Серия) в свойствах группировки необходимо найти опцию, связанную с иерархией. В СКД мы можем указать, что группировка является иерархической, и определить поля, по которым строится эта иерархия.
Для этого в свойствах поля группировки (например, Серия) мы можем указать поле-родитель. В нашем случае, это будет поле СерияРодитель. СКД автоматически построит дерево на основе этих двух полей.
Если в регистре сведений СерияРодитель не заполнено, СКД поймет, что это элемент верхнего уровня и будет отображать его как корень иерархии.
Вложенные группировки или детальные записи: Внутри группировки по Серии мы можем добавить либо вложенные группировки (если иерархия имеет много уровней и нам нужно группировать дальше), либо детальные записи. Детальные записи будут отображать все остальные поля из нашего набора данных для каждой конкретной серии.
Например, структура может выглядеть так:
Серия (с указанием СерияРодитель как поля родителя)
Номенклатура, Количество, Сумма)Важно помнить, что мы можем использовать несколько полей для группировки, но для построения именно древовидной иерархии требуется одно поле, которое является потомком, и одно поле, которое является его родителем.
При правильной настройке СКД сможет самостоятельно отобразить иерархию, где элементы будут вложены друг в друга в соответствии с их родительскими связями.
Проблема дублирования данных, когда один и тот же элемент выводится и как родительская группа, и как отдельная строка, является одной из самых распространенных при построении иерархических отчетов в СКД по произвольным данным. Это происходит потому, что СКД пытается отобразить элемент одновременно как часть иерархической структуры (заголовок группы) и как обычный элемент данных (детальную строку). Давайте разберем несколько методов, которые помогут нам справиться с этой проблемой.
Одним из эффективных способов контроля вывода данных является создание пользовательских полей в СКД. Мы можем создать вычисляемое поле, которое будет определять родителя для каждой строки отчета, или поле, которое будет использоваться исключительно для отображения иерархии, отделяя его от детальных данных.
Для этого мы можем:
Создать новое вычисляемое поле: В схеме компоновки данных в закладке "Поля" добавим новое поле. Пусть это будет СерияДляИерархии.
Определить выражение для поля: Для этого поля мы можем использовать выражение, которое будет работать в контексте группировки. Например, если мы хотим, чтобы серия отображалась только один раз на уровне группировки, мы можем использовать выражения, которые учитывают текущий уровень иерархии или уникальные идентификаторы.
В некоторых случаях, если нам нужно получить значение поля родителя для текущего элемента на определенном уровне иерархии, мы можем использовать выражения вроде ВычислитьВыражение("ВашеПоле", , "ГруппировкаРодитель"), хотя для стандартной иерархии по полям Серия и СерияРодитель СКД обычно сама справляется с этим.
Более практичный подход для предотвращения дублей: Мы можем использовать вычисляемое поле для идентификации уникальных элементов, которые должны быть показаны только один раз. Например, мы можем создать поле ИдентификаторСерии, которое будет уникальным для каждой серии. При построении группировок мы можем группировать по этому идентификатору, а не напрямую по серии, если нам нужно подавить дублирование.
Настроить отображение: В настройках отчета мы можем указать, какие поля отображать на уровне группировки, а какие – на уровне детальных записей. Например, для группировки по Серия мы можем выводить только Серия, а остальные поля (Номенклатура, Количество) оставлять для детальных записей. Таким образом, сама серия будет отображаться как заголовок группы, а ее подробности – внутри.
СКД предоставляет системное поле Уровень(), которое возвращает текущий уровень вложенности записи в иерархической структуре. Это очень мощный инструмент для управления выводом данных и борьбы с дублями.
Мы можем использовать Уровень() в отборах для группировок или детальных записей:
Отображение только элементов верхнего уровня: Если мы хотим видеть только элементы, у которых нет родителей (то есть СерияРодитель пустое), мы можем использовать отбор по полю СерияРодитель ИС NULL или НЕ ЗАПОЛНЕНО.
Контроль детальных записей: Если группировка уже выводит заголовок для родительского элемента, нам, возможно, не нужно, чтобы этот же элемент отображался как детальная запись. Мы можем создать отбор для детальных записей, который будет исключать те записи, которые уже являются родительскими для какого-либо другого элемента, или использовать отбор по Уровень().
Например, если группировка по Серии уже показывает саму серию, то для детальных записей, находящихся внутри этой группировки, мы можем применить отбор, чтобы не повторять родительскую серию как детальную запись. Это может быть реализовано через механизм "Использовать только в иерархии" или путем исключения полей из детальных записей, если они уже присутствуют в заголовке группы.
Пример использования Уровень(): Допустим, мы хотим, чтобы в отчете отображались только элементы, которые находятся на первом уровне иерархии (корневые элементы), и их непосредственные потомки. Мы можем добавить отбор для детальных записей или для группировок:
// Пример отбора для группировки "Серия"
// чтобы показывать только элементы, у которых нет явного родителя (корни)
СерияРодитель ИС NULL ИЛИ СерияРодитель = &ПустаяСерия
// Пример отбора для детальных записей,
// чтобы исключить записи, которые уже были показаны как группы верхнего уровня
Уровень() > 1
Этот отбор Уровень() > 1 для детальных записей внутри группировки означает, что мы не будем показывать "детали" для тех элементов, которые являются корневыми и уже отображены на первом уровне иерархии. Мы будем видеть только детали для вложенных элементов.
В случае, если отчет не требует сложной агрегации данных, а его основная цель – показать уникальные элементы в иерархической структуре, использование ключевого слова РАЗЛИЧНЫЕ (DISTINCT) в запросе набора данных может быть очень полезным. Это поможет выбрать только уникальные комбинации полей и уменьшить количество дублирующихся строк.
Пример использования в запросе:
ВЫБРАТЬ РАЗЛИЧНЫЕ
НашРегистрСведенийПоСериям.Серия КАК Серия,
НашРегистрСведенийПоСериям.СерияРодитель КАК СерияРодитель
ИЗ
РегистрСведений.НашРегистрСведенийПоСериям КАК НашРегистрСведенийПоСериям
Важно: этот метод подходит, если дублирование возникает из-за множественных записей одних и тех же связей в регистре сведений, и если агрегированные данные (Количество, Сумма) не должны быть рассчитаны для каждой уникальной пары родитель-потомок, а для всего уникального набора. Если же нам нужны агрегированные данные, то РАЗЛИЧНЫЕ может исказить результаты расчетов, так как он схлопнет строки до уникальных комбинаций полей, указанных в выборке.
Зачастую проблема дублирования данных кроется в некорректной настройке связей между наборами данных или в ошибках при определении полей для группировок. Мы должны внимательно перепроверить следующие аспекты:
Выражения связей: Убедитесь, что выражения связей между наборами данных (если их несколько) или между полями группировок однозначно идентифицируют отношения "родитель-потомок". Например, если мы используем поле СерияРодитель, оно должно точно указывать на поле Серия другого элемента.
Соответствие типов данных: Типы данных полей, по которым строятся связи, должны быть идентичны (например, оба поля типа СправочникСсылка.СерииНоменклатуры).
Уникальность ключей: Если ваш регистр сведений может содержать одинаковые комбинации Серия и СерияРодитель для разных Номенклатур, это может привести к дублированию. В таких случаях нам потребуется либо уточнить ключи группировки, либо использовать дополнительные отборы.
Проверка детальных записей: Иногда дубли появляются из-за того, что детальные записи отображают те же данные, что и заголовок группы. Мы должны убедиться, что в детальных записях выводятся только те поля, которые дополняют информацию, а не повторяют группировку.
Тщательная проверка этих настроек поможет исключить многие причины дублирования.
В ходе обсуждения на форуме возник вопрос о целесообразности использования регистра сведений для учета себестоимости. Давайте разберем этот аспект подробнее, поскольку это критически важный момент для понимания архитектуры системы.
В системе 1С существуют различные типы регистров, каждый из которых предназначен для своих целей:
Регистры сведений (РС): Предназначены для хранения вспомогательной информации, которая не требует учета изменений во времени (или требует, но для отображения периодических срезов). Они хранят факты, характеристики, настройки – то, что можно описать комбинацией измерений и получить "срез" данных на определенный момент или в целом. Регистры сведений не предназначены для накопления количественных или суммовых показателей в традиционном бухгалтерском или управленческом смысле.
Регистры накопления (РН): Это основной инструмент для учета количественных или стоимостных изменений. Они используются для хранения остатков и оборотов (например, остатков товаров на складах, денежных средств, себестоимости). Если нам нужно получать себестоимость или ее изменения, то основной инструмент – это именно регистр накопления.
Таким образом, использование регистра сведений напрямую для расчета себестоимости, как это подразумевалось в сообщении, является нетипичным подходом и может потребовать значительных дополнительных программных доработок для корректной интеграции с общими механизмами учета затрат в 1С.
В типовых конфигурациях 1С (например, 1С:ERP Управление предприятием, 1С:Комплексная автоматизация) предусмотрены мощные механизмы для учета себестоимости, в том числе и в разрезе серий номенклатуры. Для его использования обычно требуется:
Настройка "Политики учета серий": В карточке вида номенклатуры или самой номенклатуры необходимо указать, что для данной номенклатуры (или ее вида) должен вестись учет серий, и что себестоимость должна учитываться в разрезе серий.
Использование регистров накопления: Все движения по себестоимости, включая распределение затрат, списание по методам ФИФО/ЛИФО/по средней, отражаются в специализированных регистрах накопления (например, СебестоимостьТоваров, ЗатратыНаВыпуск). Именно эти регистры являются источником данных для расчета себестоимости.
Если мы планируем получать себестоимость по сериям, то наш регистр сведений с иерархией по сериям может служить лишь дополнительным источником аналитики или настроек. Например, он может хранить информацию о том, какая серия является "родительской" для другой, но сами движения себестоимости должны быть зафиксированы в регистрах накопления.
Расчет себестоимости в 1С:ERP и других современных конфигурациях — это сложный процесс, который включает в себя:
Регистры учета затрат: Хранят информацию о прямых и косвенных затратах.
Ключи аналитики учета затрат: Позволяют детализировать затраты по различным разрезам (номенклатура, серия, подразделение, статья затрат и т.д.).
Графы затрат: Определяют последовательность и правила распределения затрат.
Документы "Расчет себестоимости выпуска": Выполняют окончательный расчет себестоимости по месяцу.
Документы, которые фиксируют записи в ваш регистр сведений, могут быть лишь частью более крупной цепочки, которая в итоге формирует движения в регистрах накопления и регистрах себестоимости. Использование регистра сведений для хранения иерархии серий – это отличное решение для аналитического отчета. Но для полноценного учета себестоимости, особенно с учетом методов списания, распределения затрат и других нюансов, мы должны использовать стандартные механизмы 1С с регистрами накопления и специализированными обработками расчета себестоимости.
Если наш регистр сведений предназначен исключительно для построения аналитической иерархии, то это вполне допустимо. Если же он задумывается как первичный источник для получения себестоимости, то этот подход потребует глубокой переработки и интеграции с основными механизмами учета, что часто нецелесообразно и усложняет сопровождение системы.
Таким образом, мы рассмотрели, как построить иерархический отчет в СКД на основе произвольных данных из регистра сведений, а также как бороться с распространенной проблемой дублирования данных. Мы также затронули вопрос о корректном использовании регистров для учета себестоимости. Надеемся, эти рекомендации помогут вам в вашей работе!