В практике разработки на платформе 1С:Предприятие часто возникает задача распределения одной величины (например, общей суммы оплаты) на несколько составляющих (например, на разных ответственных или разные номенклатурные позиции) пропорционально их доле в общем объеме. Основная сложность здесь заключается не в самой математической пропорции, а в проблеме «последней копейки»: при округлении расчетных данных сумма долей может не совпасть с исходным итогом на 1–2 копейки. Рассмотрим, как правильно реализовать этот алгоритм программно.
Представим ситуацию: в одном документе продажи зафиксирована выручка по нескольким сотрудникам. Клиент оплачивает покупку разными способами (например, частично наличными, частично картой). Нам необходимо получить детализацию: сколько «наличных» и сколько «безналичных» денег относится к каждому сотруднику пропорционально их вкладу в общую продажу.
Таблица «Продажи»:
Таблица «Оплаты»:
Проанализируем логику: общая сумма продаж совпадает с суммой оплат. Нам нужно распределить 40 рублей наличных в пропорции 30/120 и 90/120, и аналогично распределить 80 рублей оплаты картой.
Для того чтобы итоговые суммы всегда сходились до копейки, мы применим метод последовательного вычитания. Разберем этот алгоритм по шагам:
БазаРаспределения — это общая сумма всех продаж в документе.ОстатокРаспределяемойСуммы.Округление(СуммаПродажи * СуммаОплаты / БазаРаспределения, 2).ОстатокРаспределяемойСуммы и уменьшаем БазаРаспределения на сумму текущей продажи.ОстатокРаспределяемойСуммы. Это гарантирует, что сумма всех частей будет в точности равна исходному итогу.Рассмотрим пример кода, который реализует данную логику. Допустим, у нас есть две таблицы значений: тзПродаж и тзОплат. Нам нужно заполнить результирующую таблицу тзИтог.
// Подготовим таблицу для результата
тзИтог = Новый ТаблицаЗначений;
тзИтог.Колонки.Добавить("Ответственный");
тзИтог.Колонки.Добавить("ВидОплаты");
тзИтог.Колонки.Добавить("Сумма");
// Вычисляем общую базу распределения
ОбщаяСуммаПродаж = тзПродаж.Итог("СуммаПродаж");
// Если продаж нет, распределение невозможно
Если ОбщаяСуммаПродаж = 0 Тогда
Возврат;
КонецЕсли;
Для Каждого СтрОплата Из тзОплат Цикл
ОстатокКРаспределению = СтрОплата.СуммаОплат;
ТекущаяБаза = ОбщаяСуммаПродаж;
КоличествоСтрок = тзПродаж.Количество();
Счетчик = 0;
Для Каждого СтрПродажа Из тзПродаж Цикл
Счетчик = Счетчик + 1;
НоваяСтрока = тзИтог.Добавить();
НоваяСтрока.Ответственный = СтрПродажа.Ответственный;
НоваяСтрока.ВидОплаты = СтрОплата.ВидОплаты;
// Если это последняя строка, отдаем ей весь остаток без расчетов
Если Счетчик = КоличествоСтрок Тогда
РаспределеннаяСумма = ОстатокКРаспределению;
Иначе
// Вычисляем пропорцию
РаспределеннаяСумма = Окр(СтрОплата.СуммаОплат * СтрПродажа.СуммаПродаж / ОбщаяСуммаПродаж, 2);
// Важный момент: нельзя распределить больше, чем осталось
РаспределеннаяСумма = Мин(РаспределеннаяСумма, ОстатокКРаспределению);
КонецЕсли;
НоваяСтрока.Сумма = РаспределеннаяСумма;
// Уменьшаем остаток для следующих итераций
ОстатокКРаспределению = ОстатокКРаспределению - РаспределеннаяСумма;
КонецЦикла;
КонецЦикла;
Если ваша конфигурация использует Библиотеку стандартных подсистем (БСП), задачу можно решить значительно проще. В программном интерфейсе БСП существует готовая функция, которая уже учитывает все нюансы округления и распределения остатков.
Рассмотрим метод ОбщегоНазначения.РаспределитьСуммуПропорционально(). Проанализируем, как передать в него наши данные:
// Массив коэффициентов — это наши суммы продаж
МассивКоэффициентов = тзПродаж.ВыгрузитьКолонку("СуммаПродаж");
СуммаКРаспределению = 40; // Например, для наличных
// Функция возвращает массив распределенных сумм
РаспределенныеСуммы = ОбщегоНазначения.РаспределитьСуммуПропорционально(СуммаКРаспределению, МассивКоэффициентов);
// Теперь можно перенести данные в результирующую таблицу
Для Индекс = 0 По РаспределенныеСуммы.ВГраница() Цикл
// РаспределенныеСуммы[Индекс] соответствует тзПродаж[Индекс]
КонецЦикла;
Использование этого метода считается «хорошим тоном» при разработке в среде 1С, так как код становится более читаемым, а вероятность ошибки в математических расчетах сводится к нулю.
При реализации распределения важно учитывать специфические ситуации, которые могут возникнуть в реальном учете:
ОбщаяСуммаПродаж не равна нулю, прежде чем выполнять деление. В противном случае система вызовет критическую ошибку.(A * B) / C более точно, чем (B / C) * A, так как уменьшается погрешность промежуточного округления.Проанализировав эти подходы, мы видим, что задача распределения оплат сводится к аккуратному управлению остатком. Использование циклов с вычитанием или готовых функций БСП позволяет гарантировать целостность данных в регистрах накопления, куда впоследствии будут записаны эти результаты.