В процессе разработки отчетов по продажам программисты часто сталкиваются с ситуацией, когда данные из разных регистров имеют разную степень детализации. Типичный пример: регистр Продажи содержит измерение Менеджер, а регистр ПродажиСебестоимость — нет. При попытке соединить эти таблицы в одном запросе возникает проблема: себестоимость «схлопывается» или дублируется, что приводит к неверным итогам. В этой статье мы подробно разберем, как решить эту задачу различными методами.
Рассмотрим ситуацию на конкретном примере. Предположим, в одном документе реализации были проданы «Тапочки меховые» в количестве 3 штук. Из них 1 штука закреплена за менеджером Ивановым, а 2 штуки — за Петровым (это возможно, если менеджер указывается в табличной части документа). В регистре Продажи мы увидим две строки. Однако в регистре ПродажиСебестоимость аналитика по менеджеру отсутствует, и там будет зафиксирована одна запись на общую сумму себестоимости для всех 3 штук.
При обычном левом или внутреннем соединении по номенклатуре и регистратору мы получим ситуацию, когда к каждой строке продаж (и Иванова, и Петрова) прицепится вся сумма себестоимости документа, либо данные распределятся непредсказуемо при использовании агрегатных функций. Наша задача — распределить общую себестоимость пропорционально количеству проданного товара каждым менеджером.
Проанализируем подход, предложенный автором темы. Суть метода заключается в расчете коэффициента на основе количества. Если в одной строке регистра продаж количество меньше, чем общее количество в регистре себестоимости по этому же товару и документу, мы вычисляем долю стоимости.
Рассмотрим пример реализации такого запроса:
ВЫБРАТЬ
ПродажиОбороты.Организация КАК Организация,
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.Регистратор КАК Регистратор,
ПродажиОбороты.Менеджер КАК Менеджер,
ПродажиОбороты.КоличествоОборот КАК КоличествоПродажи,
ТаблицаСебестоимости.ОбщееКоличество КАК ОбщееКоличество,
ПродажиОбороты.СтоимостьОборот КАК Выручка,
// Вычисляем долю себестоимости пропорционально количеству
ВЫБОР
КОГДА ЕСТЬNULL(ТаблицаСебестоимости.ОбщееКоличество, 0) > 0
ТОГДА ЕСТЬNULL(ТаблицаСебестоимости.ОбщаяСебестоимость, 0)
/ ТаблицаСебестоимости.ОбщееКоличество
* ПродажиОбороты.КоличествоОборот
ИНАЧЕ 0
КОНЕЦ КАК СебестоимостьРасчетная
ИЗ
РегистрНакопления.Продажи.Обороты(&ДатаНач, &ДатаКон, Регистратор, ) КАК ПродажиОбороты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
Номенклатура КАК Номенклатура,
Регистратор КАК Регистратор,
СУММА(Количество) КАК ОбщееКоличество,
СУММА(Стоимость) КАК ОбщаяСебестоимость
ИЗ
РегистрНакопления.ПродажиСебестоимость
ГДЕ
Период МЕЖДУ &ДатаНач И &ДатаКон
СГРУППИРОВАТЬ ПО
Номенклатура,
Регистратор
) КАК ТаблицаСебестоимости
ПО ПродажиОбороты.Номенклатура = ТаблицаСебестоимости.Номенклатура
И ПродажиОбороты.Регистратор = ТаблицаСебестоимости.Регистратор
Важный нюанс: В этом подходе мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ по полям Номенклатура и Регистратор. Обратите внимание, что в подзапросе себестоимости мы предварительно суммируем все записи, чтобы получить «вес» документа в целом. Это позволяет избежать дублирования сумм при соединении с несколькими менеджерами.
Использование подзапросов в соединении (как в примере выше) считается плохой практикой в 1С, так как это затрудняет работу оптимизатора СУБД. Разберем, как сделать этот запрос более «правильным» с точки зрения производительности.
Проанализируем преимущество этого метода: система сначала подготовит два компактных набора данных, проиндексирует их по регистратору и номенклатуре, и только потом выполнит соединение. Это значительно ускорит выполнение отчета на больших объемах данных.
Если отчет планируется использовать часто и на больших базах, постоянные сложные вычисления в запросах могут перегрузить сервер. В таком случае стоит рассмотреть изменение структуры регистра ПродажиСебестоимость.
Здесь есть два пути:
Как отмечают участники обсуждения, если конфигурация нетиповая (или находится на поддержке с возможностью изменений), добавление аналитики в регистр — наиболее надежный путь, исключающий ошибки округления при делении в запросах.
При реализации любого из предложенных решений следует учитывать специфику работы механизмов себестоимости в типовых решениях (например, в УПП или КА):
1. Деление на ноль. Всегда проверяйте ОбщееКоличество на равенство нулю. В некоторых случаях (например, при доп. расходах) в регистре себестоимости может быть сумма, но не быть количества. В таких ситуациях математическое распределение через пропорцию может не сработать.
2. Корректировки стоимости. Помните, что окончательная себестоимость часто формируется в конце месяца регламентным документом (например, «Расчет себестоимости»). Этот документ может делать записи без привязки к конкретным строкам табличной части реализации. Если вы соединяете данные по полю Регистратор, убедитесь, что корректировочные записи также имеют ссылку на исходный документ продажи.
3. Возвраты товаров. При наличии возвратов в одном периоде обороты могут «схлопываться». Для корректного анализа лучше использовать детализацию до регистратора и анализировать знаки операций (приход/расход), чтобы возврат одного менеджера не уменьшил себестоимость продаж другого.
4. Округление. При делении суммы себестоимости на общее количество и последующем умножении на долю менеджера могут возникать погрешности в копейках. Для финансовой отчетности это может быть критично. Если требуется абсолютная точность, последний менеджер в списке должен получать «остаток» от общей суммы себестоимости, чтобы сумма частей всегда равнялась целому.
Выяснив причину неверной группировки и выбрав подходящий метод (запрос с распределением или доработка регистра), вы сможете построить точный аналитический отчет, который позволит оценивать эффективность каждого менеджера с учетом реальной маржинальности продаж.