В практике разработки на платформе 1С часто возникает задача автоматического распределения суммы поступившего платежа (например, по Расходному кассовому ордеру или Списанию с расчетного счета) по накопленным долгам. Этот процесс обычно называют методом FIFO (First In, First Out — первым пришел, первым ушел) или зачетом авансов. Рассмотрим подробно, как реализовать такой алгоритм, начиная от простой логики перебора и заканчивая оптимизированными запросами.
Представим ситуацию, описанную в примере: у нас есть общая сумма к распределению (5 000 рублей) и список неоплаченных документов-оснований (например, Поступление товаров и услуг), отсортированных по дате возникновения задолженности:
Проанализируем процесс поэтапно:
Если бы после всех манипуляций у нас осталась лишняя сумма (например, платеж был бы 6 000), этот остаток должен быть зафиксирован как аванс.
Самый простой и понятный способ реализации — это получение списка долгов через запрос к регистрам накопления и последующая обработка результата в коде. Разберем по шагам, как это выглядит в программном коде 1С.
Сначала подготовим запрос к остаткам регистра (например, ВзаиморасчетыСПоставщиками.Остатки). Важно получить данные в разрезе документов-оснований и отсортировать их по моменту времени.
// Подготовка запроса для получения остатков долга
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВзаиморасчетыОстатки.ДокументОснование КАК Документ,
| ВзаиморасчетыОстатки.СуммаВзаиморасчетовОстаток КАК ОстатокДолга
|ИЗ
| РегистрНакопления.ВзаиморасчетыСПоставщиками.Остатки(&Период, Контрагент = &Контрагент) КАК ВзаиморасчетыОстатки
|УПОРЯДОЧИТЬ ПО
| ВзаиморасчетыОстатки.ДокументОснование.Дата,
| ВзаиморасчетыОстатки.ДокументОснование.Ссылка";
Запрос.УстановитьПараметр("Период", МоментВремени());
Запрос.УстановитьПараметр("Контрагент", Объект.Контрагент);
Выборка = Запрос.Выполнить().Выбрать();
ОстатокПлатежа = Объект.СуммаДокумента;
Пока Выборка.Следующий() И ОстатокПлатежа > 0 Цикл
СуммаКЗачету = Мин(Выборка.ОстатокДолга, ОстатокПлатежа);
// Здесь логика записи распределения, например, в табличную часть или движения
НоваяСтрока = Объект.РасшифровкаПлатежа.Добавить();
НоваяСтрока.Документ = Выборка.Документ;
НоваяСтрока.Сумма = СуммаКЗачету;
ОстатокПлатежа = ОстатокПлатежа - СуммаКЗачету;
КонецЦикла;
// Если после цикла ОстатокПлатежа > 0, записываем его как аванс
Если ОстатокПлатежа > 0 Тогда
// Логика обработки аванса
КонецЕсли;
Для высоконагруженных систем перебор в цикле может быть неэффективным. Рассмотрим более продвинутый метод — вычисление распределения прямо в запросе с помощью нарастающего итога. Это позволяет одним обращением к базе данных получить уже готовые суммы к списанию для каждого документа.
Проанализируем ситуацию: нам нужно понять, в какой диапазон «накопленного долга» попадает наш платеж. Мы соединяем таблицу долгов саму с собой так, чтобы для каждой строки посчитать сумму всех предыдущих строк.
// Пример логики запроса с нарастающим итогом
ВЫБРАТЬ
Т1.Документ КАК Документ,
Т1.СуммаДолга КАК СуммаДолга,
СУММА(Т2.СуммаДолга) КАК НарастающийИтог
ПОМЕСТИТЬ ВТ_НарастающийИтог
ИЗ
ВТ_Долги КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Долги КАК Т2
ПО Т1.ДатаДокумента >= Т2.ДатаДокумента
И Т1.Ссылка >= Т2.Ссылка // Важно для уникальности при совпадении дат
СГРУППИРОВАТЬ ПО
Т1.Документ, Т1.СуммаДолга;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Документ,
ВЫБОР
КОГДА &ОбщаяСумма >= ВТ.НарастающийИтог ТОГДА ВТ.СуммаДолга
КОГДА &ОбщаяСумма > (ВТ.НарастающийИтог - ВТ.СуммаДолга) ТОГДА &ОбщаяСумма - (ВТ.НарастающийИтог - ВТ.СуммаДолга)
ИНАЧЕ 0
КОНЕЦ КАК СуммаКРаспределению
ИЗ
ВТ_НарастающийИтог КАК ВТ
ГДЕ
ВТ.НарастающийИтог - ВТ.СуммаДолга < &ОбщаяСумма
Разберем работу этого условия:
При реализации алгоритма списания денег крайне важно учитывать детали, которые могут повлиять на корректность учета и производительность системы.
1. Уникальность сортировки
Выясним причину, по которой нельзя сортировать только по дате. В 1С несколько документов могут быть созданы в одну и ту же секунду. Если использовать только Дата, порядок документов в запросе может меняться при разных запусках. Это приведет к непредсказуемому поведению при перепроведении. Поэтому в УПОРЯДОЧИТЬ ПО всегда добавляйте Ссылка или МоментВремени.
2. Блокировки данных
Посмотрим на ситуацию со стороны многопользовательской работы. Если два бухгалтера одновременно вводят оплату по одному и тому же контрагенту, они оба могут считать одни и те же остатки задолженности. В итоге один и тот же документ будет «оплачен» дважды. Чтобы этого избежать, перед началом расчета распределения необходимо устанавливать исключительную блокировку на регистр накопления по данному контрагенту.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ВзаиморасчетыСПоставщиками");
ЭлементБлокировки.УстановитьПараметр("Контрагент", Объект.Контрагент);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
3. Учет НДС и валют
Проанализируем ситуацию с частичной оплатой. Если мы оплачиваем документ частично (как в примере 500 из 700), необходимо также пропорционально распределить сумму НДС. Это важно для корректного формирования книги покупок или продаж. Формула проста: НДС_Частичный = (Сумма_К_Оплате / Сумма_Документа_Всего) * Сумма_НДС_Всего. Однако при таких расчетах часто возникают «копейки» из-за округления, которые нужно списывать на последний оплачиваемый документ или корректировать отдельно.
4. Обработка авансов
Если после распределения по всем документам долга у вас осталась неизрасходованная сумма платежа, ее нельзя просто игнорировать. В системе 1С такие суммы обычно относятся на специальные счета учета авансов (например, 60.02 для поставщиков или 62.02 для покупателей). В регистрах накопления при этом часто очищается измерение ДокументОснование (или ставится ссылка на сам платежный документ), что сигнализирует системе о наличии аванса, который будет зачтен при поступлении новых документов долга.
Рассмотренный нами алгоритм является базовым для большинства типовых конфигураций. Мы разбили задачу на понятные этапы: получение остатков, их упорядочивание и последовательное вычитание сумм. Для простых задач используйте цикл, а для высокопроизводительных решений — запросы с нарастающим итогом. Помните о блокировках и точности распределения НДС, чтобы ваш учет всегда оставался корректным.