При обработке выписок банка или оформлении документов поступления оплаты часто возникает задача распределения общей введенной суммы по строкам табличной части. Проблема может решаться двумя базовыми путями: последовательным разнесением задолженности по графику (метод FIFO) либо точным математическим подбором комбинации сумм, дающих в итоге исходную сумму оплаты (классическая задача о сумме подмножеств или «задача о рюкзаке»). Разберем подробно оба метода, их техническую реализацию в платформе 1С и практические нюансы.
С точки зрения методики бухгалтерского и управленческого учета наиболее корректным считается разнесение поступающих платежей по датам возникновения задолженности или по очередности документов в таблице. Проанализируем алгоритм последовательного погашения:
Рассмотрим пример реализации такого алгоритма в коде 1С:
ОстатокРаспределения = ОбщаяСуммаОплаты;
Для Каждого СтрокаТаблицы Из Объект.Платежи Цикл
Если ОстатокРаспределения <= 0 Тогда
СтрокаТаблицы.СуммаОплаты = 0;
Продолжить;
КонецЕсли;
СуммаКРаспределению = Мин(СтрокаТаблицы.СуммаДокумента, ОстатокРаспределения);
СтрокаТаблицы.СуммаОплаты = СуммаКРаспределению;
ОстатокРаспределения = ОстатокРаспределения - СуммаКРаспределению;
КонецЦикла;
Преимущества метода: простота реализации, высокий быстродействие, отсутствие риска зависания системы при большом количестве строк.
Недостатки: если поступившая сумма должна покрывать конкретные платежи полностью без образования частичных остатков (когда оплата пришла за конкретные счета), простой последовательный цикл может оставить нерассчитанный копеечный или частичный остаток на одной из строк.
Если требуется найти среди множества строк именно те, сумма которых в точности равна введенному значению (без частичного гашения), мы сталкиваемся с комбинаторной задачей о сумме подмножеств (Subset Sum Problem). Каждая строка либо полностью входит в искомый набор (значение 1), либо пропускается (значение 0).
Проанализируем задачу математически: для списка строк с суммами A1, A2, ..., AN нужно найти такие бинарные коэффициенты Xi ∈ {0, 1}, чтобы выдерживалось равенство:
СУММА(Ai * Xi) = B, где B — искомая сумма платежа.
Для небольшого количества строк (до 15–20 элементов) можно применить итеративный или рекурсивный перебор комбинаций. Посмотрим, как написать рекурсивный поиск комбинации в 1С:
Функция НайтиКомбинацию(МассивСумм, Индекс, ТекущаяСумма, ЦелеваяСумма, ВыбранныеИндексы)
Если ТекущаяСумма = ЦелеваяСумма Тогда
Возврат Истина;
КонецЕсли;
Если ТекущаяСумма > ЦелеваяСумма Или Индекс > МассивСумм.В Граница() Тогда
Возврат Ложь;
КонецЕсли;
// Вариант 1: включаем текущую строку в набор
ВыбранныеИндексы.Добавить(Индекс);
Если НайтиКомбинацию(МассивСумм, Индекс + 1, ТекущаяСумма + МассивСумм[Индекс], ЦелеваяСумма, ВыбранныеИндексы) Тогда
Возврат Истина;
КонецЕсли;
// Вариант 2: пропускаем текущую строку
ВыбранныеИндексы.Удалить(ВыбранныеИндексы.Количество() - 1);
Возврат НайтиКомбинацию(МассивСумм, Индекс + 1, ТекущаяСумма, ЦелеваяСумма, ВыбранныеИндексы);
КонецФункции
При вызове данной функции передается исходный массив сумм строк и пустой массив ВыбранныеИндексы. Если функция возвращает Истина, в массиве ВыбранныеИндексы будут содержаться номера элементов, сумма которых точно дает запрашиваемый результат.
При реализации точного подбора сумм в реальных базах данных следует учитывать особенности работы платформы 1С:
Массив или ТаблицаЗначений.ФоновыеЗадания, чтобы не блокировать интерфейс пользователя.