Как распределить платежи по материалам пропорционально их стоимости в запросе 1С

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

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

Анализируем исходную задачу и математическую модель

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

Алгоритм распределения должен состоять из следующих шагов:

  1. Определение "жестких" сумм: Суммируем все платежи, которые пользователь уже назначил конкретной номенклатуре.
  2. Расчет остаточной потребности: Из общей стоимости каждого материала вычитаем сумму его "жестких" платежей. Это даст нам базу для дальнейшего пропорционального распределения.
  3. Определение базы распределения: Суммируем все остаточные потребности по всем материалам.
  4. Пропорциональное деление: Для каждой даты, где номенклатура не указана, берем сумму платежа и умножаем ее на долю конкретного материала в общей остаточной потребности.
  5. Объединение результатов: Складываем "жесткие" платежи и распределенные суммы в единую результирующую таблицу.

Разберем на примере из вопроса. Кирпич стоит 5000, но на дату 01.02.2012 по нему уже зафиксирован платеж 4000. Значит, его "остаточная стоимость" для распределения общих денег — 1000. Аналогично считаем для Бензина (3000) и Лопаты (2000). Общая база распределения: 1000 + 3000 + 2000 = 6000.

Реализация логики через временные таблицы в запросе

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

Шаг 1: Подготовка данных о номенклатуре и ее "свободных" остатках. Выясним, сколько денег "не закрыто" по каждой позиции.


ВЫБРАТЬ
    Т1.Номенклатура,
    Т1.Стоимость КАК ОбщаяСтоимость,
    ЕСТЬNULL(СуммыЖесткихПлатежей.Сумма, 0) КАК УплаченоЖестко,
    Т1.Стоимость - ЕСТЬNULL(СуммыЖесткихПлатежей.Сумма, 0) КАК ОстатокДляРаспределения
ПОМЕСТИТЬ ОстаткиПотребности
ИЗ
    ТаблицаМатериалов КАК Т1
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            Т2.Номенклатура,
            СУММА(Т2.Сумма) КАК Сумма
        ИЗ
            ТаблицаПлатежей КАК Т2
        ГДЕ
            Т2.Номенклатура <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
        СГРУППИРОВАТЬ ПО
            Т2.Номенклатура
    ) КАК СуммыЖесткихПлатежей
    ПО Т1.Номенклатура = СуммыЖесткихПлатежей.Номенклатура
;

Шаг 2: Получение общей суммы базы для распределения. Нам нужно одно число — сумма всех ОстатокДляРаспределения.


ВЫБРАТЬ
    СУММА(ОстаткиПотребности.ОстатокДляРаспределения) КАК ОбщаяБаза
ПОМЕСТИТЬ БазаРаспределения
ИЗ
    ОстаткиПотребности КАК ОстаткиПотребности
;

Финальное распределение и сборка результата

Теперь мы можем объединить наши "неявные" платежи с базой и рассчитать доли. Рассмотрим подробнее финальный запрос. Важно помнить, что мы должны вывести и те строки, которые были указаны явно, и те, что мы распределили.


// 1. Сначала берем платежи с указанной номенклатурой
ВЫБРАТЬ
    Т2.ДатаПлатежа,
    Т2.Сумма,
    Т2.Номенклатура
ИЗ
    ТаблицаПлатежей КАК Т2
ГДЕ
    Т2.Номенклатура <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)

ОБЪЕДИНИТЬ ВСЕ

// 2. Распределяем платежи без номенклатуры
ВЫБРАТЬ
    Т2.ДатаПлатежа,
    ВЫРАЗИТЬ(Т2.Сумма * (Остатки.ОстатокДляРаспределения / База.ОбщаяБаза) КАК ЧИСЛО(15, 2)),
    Остатки.Номенклатура
ИЗ
    ТаблицаПлатежей КАК Т2
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОстаткиПотребности КАК Остатки
        ПО (ИСТИНА)
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ БазаРаспределения КАК База
        ПО (ИСТИНА)
ГДЕ
    Т2.Номенклатура = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)

Проблема округления и "копеек"

Проанализируем возможные ошибки. При использовании функции ВЫРАЗИТЬ для округления до 2 знаков в большой выборке может возникнуть ситуация, когда сумма распределенных частей не будет в точности равна исходной сумме платежа (разница в 1-2 копейки).

В профессиональной разработке 1С для решения этой проблемы применяют метод "последней строки". Суть метода: для всех строк, кроме последней, применяется стандартное распределение, а в последнюю строку записывается остаток (ИтоговаяСумма - СуммаУжеРаспределенных). В чистом запросе это можно реализовать через нумерацию строк и использование нарастающего итога, однако это существенно усложняет текст запроса. Если критична точность до копейки, рекомендуется выполнять финальную корректировку "хвоста" программно после выгрузки запроса в ТаблицаЗначений.

Современный подход: Оконные функции

Если вы работаете на платформе 1С 8.3.20 и выше, задачу можно решить элегантнее с помощью оконных функций. Это позволит избежать лишних соединений и временных таблиц для расчета общей базы. Мы можем использовать конструкцию СУММА(...) OVER(), чтобы получить общую сумму остатков прямо в основном запросе.

Пример использования оконной функции для расчета базы:


ВЫБРАТЬ
    Остатки.Номенклатура,
    Остатки.ОстатокДляРаспределения / СУММА(Остатки.ОстатокДляРаспределения) OVER() КАК КоэффициентДоли
ИЗ
    ОстаткиПотребности КАК Остатки

Резюмируем: Для корректного распределения платежей в 1С важно сначала "очистить" базу распределения от сумм, которые уже имеют жесткую привязку к аналитике. Использование временных таблиц позволяет структурировать логику, а использование ЛЕВОЕ СОЕДИНЕНИЕ гарантирует, что мы не потеряем номенклатуру, по которой еще не было оплат.

← На главную