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