Как реализовать алгоритм распределения суммы платежа по документам задолженности в 1С?

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

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

Суть задачи и логика распределения

Представим ситуацию, описанную в примере: у нас есть общая сумма к распределению (5 000 рублей) и список неоплаченных документов-оснований (например, Поступление товаров и услуг), отсортированных по дате возникновения задолженности:

  1. Документ №1 на сумму 3 000 руб.
  2. Документ №2 на сумму 1 500 руб.
  3. Документ №3 на сумму 700 руб.

Проанализируем процесс поэтапно:

  1. Берем первый документ (3 000 руб.). Сумма платежа (5 000) больше или равна долгу. Списываем 3 000 полностью. Остаток платежа: 5 000 - 3 000 = 2 000 руб.
  2. Берем второй документ (1 500 руб.). Остаток платежа (2 000) больше долга. Списываем 1 500 полностью. Остаток платежа: 2 000 - 1 500 = 500 руб.
  3. Берем третий документ (700 руб.). Остаток платежа (500) меньше суммы долга. Списываем только 500 руб. (частичная оплата). Остаток платежа становится равным 0.

Если бы после всех манипуляций у нас осталась лишняя сумма (например, платеж был бы 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 для покупателей). В регистрах накопления при этом часто очищается измерение ДокументОснование (или ставится ссылка на сам платежный документ), что сигнализирует системе о наличии аванса, который будет зачтен при поступлении новых документов долга.

Резюме

Рассмотренный нами алгоритм является базовым для большинства типовых конфигураций. Мы разбили задачу на понятные этапы: получение остатков, их упорядочивание и последовательное вычитание сумм. Для простых задач используйте цикл, а для высокопроизводительных решений — запросы с нарастающим итогом. Помните о блокировках и точности распределения НДС, чтобы ваш учет всегда оставался корректным.

← На главную