В практике разработки на платформе 1С часто возникает задача распределения общих сумм (оплат, затрат или скидок) по набору аналитик. Особую сложность представляет ситуация, когда часть платежей уже жестко привязана к конкретным позициям, а оставшуюся "общую" сумму нужно распределить между всеми материалами пропорционально их стоимости. Рассмотрим, как реализовать такую логику исключительно средствами языка запросов, без использования циклов в коде 1С.
Прежде чем приступать к написанию кода, проанализируем входные данные. Представим, что у нас есть список материалов с их общей стоимостью (Таблица 1) и график платежей (Таблица 2), где в некоторых строках номенклатура указана, а в некоторых — нет.
Алгоритм распределения должен состоять из следующих шагов:
Разберем на примере из вопроса. Кирпич стоит 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С важно сначала "очистить" базу распределения от сумм, которые уже имеют жесткую привязку к аналитике. Использование временных таблиц позволяет структурировать логику, а использование ЛЕВОЕ СОЕДИНЕНИЕ гарантирует, что мы не потеряем номенклатуру, по которой еще не было оплат.