Как правильно получить остатки по регистру без учета определенного типа регистратора в 1С?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

Разработчики в процессе проектирования сложных отчетов или механизмов распределения затрат нередко сталкиваются с необходимостью получить остатки по регистру накопления (например, по партиям товаров), исключив при этом влияние определенных документов. Типичный пример — потребность увидеть «чистую» стоимость остатка партии без учета дополнительных расходов, которые были на нее распределены документами типа ПоступлениеДопРасходов.

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

Типичная ошибка №1: Запрос к физической таблице движений

Первое, что часто приходит в голову начинающим разработчикам — это написать запрос к основной (физической) таблице регистра накопления с условием исключения конкретного типа регистратора. Код такого запроса выглядит примерно так:


// НЕПРАВИЛЬНО! Так делать крайне не рекомендуется
ВЫБРАТЬ
    Партии.Номенклатура КАК Номенклатура,
    Партии.ДокументОприходования КАК ДокументОприходования,
    СУММА(ВЫБОР 
        КОГДА Партии.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) ТОГДА Партии.Стоимость
        ИНАЧЕ -Партии.Стоимость 
    КОНЕЦ) КАК СтоимостьОстаток
ИЗ
    РегистрНакопления.ПартииТоваровНаСкладах КАК Партии
ГДЕ
    НЕ Партии.Регистратор ССЫЛКА Документ.ПоступлениеДопРасходов
ГРУППИРОВАТЬ ПО
    Партии.Номенклатура,
    Партии.ДокументОприходования

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

  1. Полное игнорирование таблиц итогов. Регистры накопления в 1С оптимизированы для мгновенного получения остатков. Платформа хранит рассчитанные итоги на конец каждого месяца в специальных служебных таблицах. При обращении к виртуальной таблице .Остатки() СУБД берет уже готовые данные. Обращаясь к физической таблице, мы заставляем СУБД сканировать абсолютно все записи регистра с момента начала ведения учета. На больших базах это гарантированно приведет к критическому падению производительности и ошибкам Time Out.
  2. Избыточные блокировки базы данных. Чтение физической таблицы движений накладывает тяжелые блокировки, что может полностью парализовать работу пользователей, проводящих документы по этому же регистру в данный момент.

Типичная ошибка №2: Использование виртуальной таблицы ОстаткиИОбороты с периодичностью по регистратору

Другой неверный путь — попытка использовать виртуальную таблицу ОстаткиИОбороты с детализацией до регистратора и последующим отбором. На первый взгляд, это кажется более «цивилизованным» методом, но здесь кроется серьезная математическая проблема.

Поле Регистратор не является измерением регистра — это лишь реквизит записи. Если мы накладываем фильтр, исключающий определенный регистратор, платформа все равно рассчитывает остатки. В результате СКД или СУБД начинает рассчитывать остатки только по разрешенным регистраторам, ломая базовое математическое правило учета: НачальныйОстаток + Приход - Расход = КонечныйОстаток. Полученные цифры не будут иметь никакого отношения к реальным остаткам на складах.

Методическая некорректность простого вычитания

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

  1. Была закуплена партия товара стоимостью 100 рублей.
  2. Документом ПоступлениеДопРасходов на нее было распределено еще 10 рублей. Текущая стоимость партии на балансе составляет 110 рублей.
  3. Затем часть партии была списана или продана (например, на сумму 104 рубля). На остатке числится товар стоимостью 6 рублей.

Если мы попытаемся вычислить остаток без учета доп. расходов путем простого вычитания всей суммы доп. расходов из текущего остатка, мы получим абсурдный результат: 6 рублей (остаток) - 10 рублей (доп. расходы) = -4 рубля. Стоимость ушла в минус, хотя товар физически еще есть на складе.

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

Решение №1: Пропорциональный расчет в запросе (без изменения метаданных)

Если мы работаем со старой или типовой конфигурацией, которую нельзя изменять (например, «Управление торговлей 10.3» или «Управление производственным предприятием 1.3»), единственным верным решением будет математический пропорциональный расчет чистой стоимости остатка.

Для этого нам необходимо собрать в запросе три параметра:

  1. Текущий остаток стоимости по партии (из таблицы .Остатки()).
  2. Первоначальную стоимость партии по документу-оприходования (приходный оборот по партии от основного документа закупки).
  3. Сумму распределенных дополнительных расходов по этой партии за все время (приходный оборот по партии от документов доп. расходов).

Формула расчета чистой стоимости выглядит следующим образом:

ЧистаяСтоимостьОстатка = СтоимостьОстаток * (ПервоначальнаяСтоимостьЗакупки / (ПервоначальнаяСтоимостьЗакупки + СуммаВсехДопРасходов))

Рассмотрим, как реализовать этот алгоритм с помощью запроса 1С:


ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.ДокументОприходования КАК ДокументОприходования,
    Остатки.КоличествоОстаток КАК КоличествоОстаток,
    Остатки.СтоимостьОстаток КАК СтоимостьОстаток,
    ЕСТЬNULL(ПервоначальнаяЗакупка.СтоимостьОборот, 0) КАК ПервоначальнаяСтоимость,
    ЕСТЬNULL(ДопРасходы.СтоимостьОборот, 0) КАК ВсегоДопРасходов
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
    РегистрНакопления.ПартииТоваровНаСкладах.Остатки(&Период, ) КАК Остатки
    
    // Получаем сумму первоначальной закупки по основному документу
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваровНаСкладах.Обороты(, &Период, Авто, ) КАК ПервоначальнаяЗакупка
        ПО Остатки.Номенклатура = ПервоначальнаяЗакупка.Номенклатура
        И Остатки.ДокументОприходования = ПервоначальнаяЗакупка.ДокументОприходования
        И ПервоначальнаяЗакупка.Регистратор = Остатки.ДокументОприходования
        
    // Получаем сумму всех доп. расходов по этой партии
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваровНаСкладах.Обороты(, &Период, Авто, ) КАК ДопРасходы
        ПО Остатки.Номенклатура = ДопРасходы.Номенклатура
        И Остатки.ДокументОприходования = ДопРасходы.ДокументОприходования
        И ДопРасходы.Регистратор ССЫЛКА Документ.ПоступлениеДопРасходов
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Данные.Номенклатура КАК Номенклатура,
    Данные.ДокументОприходования КАК ДокументОприходования,
    Данные.КоличествоОстаток КАК КоличествоОстаток,
    ВЫБОР
        КОГДА Данные.ПервоначальнаяСтоимость + Данные.ВсегоДопРасходов = 0
            ТОГДА 0
        ИНАЧЕ Данные.СтоимостьОстаток * (Данные.ПервоначальнаяСтоимость / (Данные.ПервоначальнаяСтоимость + Данные.ВсегоДопРасходов))
    КОНЕЦ КАК ЧистаяСтоимостьОстатка
ИЗ
    ВТ_ИсходныеДанные КАК Данные

Этот способ позволяет получить абсолютно точную и математически обоснованную «чистую» стоимость остатка партии без прямого обращения к физическим таблицам и без нарушения логики партионного учета.

Решение №2: Проектирование регистра с разделением ресурсов

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

Вместо одного ресурса Стоимость необходимо создать несколько независимых ресурсов:

При таком подходе приходные документы (например, ПоступлениеТоваровУслуг) будут делать движения в ресурс СтоимостьЗакупки, а документы ПоступлениеДопРасходов будут увеличивать только ресурс СтоимостьДопРасходов. При выбытии (списании, продаже) товара оба ресурса будут уменьшаться пропорционально количеству списываемого товара.

Чтобы получить остаток партии без учета дополнительных расходов в такой архитектуре, достаточно написать простейший и очень быстрый запрос к виртуальной таблице остатков:


ВЫБРАТЬ
    ПартииОстатки.Номенклатура КАК Номенклатура,
    ПартииОстатки.ДокументОприходования КАК ДокументОприходования,
    ПартииОстатки.КоличествоОстаток КАК КоличествоОстаток,
    ПартииОстатки.СтоимостьЗакупкиОстаток КАК ЧистаяСтоимостьОстатка
ИЗ
    РегистрНакопления.ПартииТоваровНаСкладах.Остатки(&Период, ) КАК ПартииОстатки

Данный подход полностью исключает необходимость выполнять тяжелые соединения таблиц оборотов и гарантирует максимальную производительность системы даже при миллионных объемах данных.

Как эта задача решена в современных конфигурациях 1С (ERP 2, КА 2, УТ 11)

В современных прикладных решениях фирмы «1С» на платформе 1С:Предприятие 8.3 партионный учет и расчет себестоимости реализованы именно по второму принципу. В этих конфигурациях больше нет старого регистра ПартииТоваровНаСкладах.

Ему на смену пришел универсальный регистр накопления СебестоимостьТоваров, в структуре которого изначально предусмотрено разделение по видам затрат. В нем присутствуют отдельные ресурсы:

Благодаря этому разработчикам современных конфигураций не приходится придумывать сложные расчетные схемы в запросах — достаточно обратиться к готовому ресурсу СтоимостьЗакупки в виртуальной таблице остатков регистра себестоимости.

← На главную