Как рассчитать итоговый остаток товара, если по разным организациям есть и плюсы, и минусы?

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

В практике учета 1С часто возникает ситуация, когда на одном складе по одной номенклатурной позиции числятся остатки в разрезе нескольких организаций. Иногда это приводит к путанице: по одной организации товар в плюсе, а по другим — в минусе. Например, по организации "А" числится 3 единицы товара, а по организациям "Б" и "В" по -1 единице. Фактически на складе лежит 1 единица товара (3 - 1 - 1 = 1). Задача — получить этот фактический остаток (1 шт.) и понять, за какой организацией он теперь числится.

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

Почему простые методы не работают?

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

1. Попытка свернуть таблицу значений методом Свернуть()

Предположим, мы получили данные из регистра в таблицу значений ТабЗнач с колонками "Номенклатура", "Организация", "Склад", "Количество".

Первый вариант — сгруппировать по номенклатуре и просуммировать количество:


РезультатЗапроса = Запрос.Выполнить();
ТабЗнач = РезультатЗапроса.Выгрузить();
ТабЗнач.Свернуть("Номенклатура", "Количество");

Этот код правильно посчитает итоговое количество (в нашем примере получится 1). Однако есть большая проблема: из таблицы пропадут колонки "Организация" и "Склад", так как они не были указаны ни в группировочных, ни в суммируемых полях. В итоге мы получим верное количество, но потеряем всю аналитику.

Второй вариант — включить в группировку все нужные поля:

ТабЗнач.Свернуть("Номенклатура, Организация, Склад", "Количество");

В этом случае метод Свернуть() не даст никакого эффекта. Поскольку в исходных данных каждая строка имеет уникальную комбинацию "Номенклатура + Организация + Склад", сворачивать будет просто нечего. Таблица останется в исходном виде.

2. Группировка в запросе 1С

Аналогичная ситуация происходит при использовании группировок в запросе. Если мы уберем поле Организация из секции СГРУППИРОВАТЬ ПО и будем использовать агрегатную функцию СУММА(КоличествоКонечныйОстаток), мы получим правильный итог — 1. Но опять встает вопрос: к какой организации отнести этот результат? Запрос не может сам принять такое решение.

Если же оставить Организация в группировке, мы получим исходные данные: три строки с остатками 3, -1 и -1.

В чем заключается основная сложность задачи?

Ключевая проблема здесь не техническая, а бизнес-логическая. Вопрос «за какой организацией числится итоговый остаток?» не имеет однозначного ответа без заранее определенных правил.

Рассмотрим другой пример, который это иллюстрирует:

Общий фактический остаток: -1 + 1 + 1 = 1 шт. Но кому он принадлежит? Организации Б или Организации В? Система не может этого знать. Возможно, отрицательный остаток организации А нужно погасить за счет остатка Б, или В, или по частям. Это должно определяться учетной политикой или специальным алгоритмом.

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

Комплексное решение: Запрос + Программная обработка

Наиболее надежный способ — это составить запрос, который подготовит нам удобную для анализа структуру данных, а затем в коде 1С реализовать логику "схлопывания" остатков.

Шаг 1. Подготовка данных с помощью запроса

Нам нужно получить все строки с положительными и отрицательными остатками, а также общую сумму отрицательных остатков, которую нужно "погасить". Это можно сделать с помощью конструкции ВЫБОР КОГДА и итогов по запросу.

Посмотрим на пример такого запроса:


ВЫБРАТЬ
    ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
    ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.СкладскаяТерритория КАК Склад,
    ВЫБОР
        КОГДА ТоварыОрганизацийОстатки.КоличествоКонечныйОстаток < 0
            ТОГДА "-"
        ИНАЧЕ "+"
    КОНЕЦ КАК ЗнакОстатка,
    ТоварыОрганизацийОстатки.Организация КАК Организация,
    ТоварыОрганизацийОстатки.КоличествоКонечныйОстаток КАК КоличествоКонечныйОстаток,
    ВЫБОР
        КОГДА ТоварыОрганизацийОстатки.КоличествоКонечныйОстаток < 0
            ТОГДА -ТоварыОрганизацийОстатки.КоличествоКонечныйОстаток
        ИНАЧЕ 0
    КОНЕЦ КАК ОтрицательныйОстаток
ИЗ
    РегистрНакопления.ТоварыОрганизаций.Остатки(&КонецПериода, ) КАК ТоварыОрганизацийОстатки
ГДЕ
    ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.Номенклатура = &Номенклатура
    И ТоварыОрганизацийОстатки.АналитикаУчетаНоменклатуры.СкладскаяТерритория = &СкладскаяТерритория

ИТОГИ
    СУММА(ОтрицательныйОстаток)
ПО
    Номенклатура,
    Склад

Разберем, что делает этот запрос:

  1. Поле ЗнакОстатка: Мы создаем вычисляемое поле, которое сразу показывает, положительный остаток в строке или отрицательный. Это может быть удобно для дальнейшей сортировки или фильтрации.
  2. Поле ОтрицательныйОстаток: Здесь мы вычисляем абсолютное значение отрицательного остатка. Если остаток -1, поле будет равно 1. Если остаток 3, поле будет равно 0.
  3. Секция ИТОГИ: Это самая важная часть. Мы просим 1С посчитать общую сумму по полю ОтрицательныйОстаток в разрезе каждой группировки "Номенклатура + Склад". В результате мы получим общую сумму "минусов", которую нужно будет списать с "плюсов". Для нашего исходного примера (остатки 3, -1, -1) итоговая сумма по ОтрицательныйОстаток будет равна 2.

Шаг 2. Программный алгоритм "схлопывания" остатков

После выполнения запроса мы получим результат, который нужно обойти и обработать. Алгоритм будет следующим:

  1. Создаем новую таблицу значений, куда будем складывать итоговые "чистые" остатки, например, ИтоговыеОстатки.
  2. Выполняем запрос и начинаем обходить его результат. Удобнее всего использовать обход по группировкам.
  3. Для каждой группировки (Номенклатура+Склад) сначала получаем итоговую сумму отрицательных остатков. Сохраняем ее в переменную, например, СуммаКПогашению. В нашем примере она будет равна 2.
  4. Начинаем обход детальных записей внутри этой группировки.
  5. Нас интересуют только строки с положительным остатком (КоличествоКонечныйОстаток > 0).
  6. Для каждой такой строки мы пытаемся погасить накопленный минус:
    • Определяем, сколько мы можем списать с текущей строки: КоличествоКСписанию = Мин(ТекущийПоложительныйОстаток, СуммаКПогашению).
    • Уменьшаем текущий остаток: НовыйОстаток = ТекущийПоложительныйОстаток - КоличествоКСписанию.
    • Уменьшаем общую сумму минусов: СуммаКПогашению = СуммаКПогашению - КоличествоКСписанию.
    • Если НовыйОстаток после списания все еще больше нуля, добавляем в таблицу ИтоговыеОстатки новую строку с этой же организацией и новым, уменьшенным остатком.
  7. Продолжаем обход, пока не пройдем все строки с положительными остатками или пока СуммаКПогашению не станет равна нулю.

В результате в таблице ИтоговыеОстатки останутся только те организации, на которых после всех взаимозачетов числится положительный остаток товара.

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

← На главную