Как исправить некорректный расчет и вывод долга контрагента в печатной форме счета на оплату?

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

При доработке печатных форм документов «Счет на оплату покупателю» или «Заказ покупателя» разработчики часто добавляют информационный блок с текущей задолженностью контрагента. Однако на практике нередко возникает ситуация: у клиента нет долгов по данным отчета «Ведомость по взаиморасчетам с контрагентами», но при формировании печатной формы в ней выводится долг, равный сумме самого выписываемого счета, либо при нулевом долге рамка с суммой вовсе пропадает. Разберем по шагам причины такого поведения алгоритмов и посмотрим, как правильно исправить код.

Причина 1. Искусственное прибавление суммы текущего документа к сумме долга

Проанализируем фрагмент исходного кода формирования печатной формы, где рассчитывается переменная СуммаДолга_:


СуммаДолга_ = ТекущийДолгПоКонтрагенту(СсылкаНаОбъект);

Если (Час(СсылкаНаОбъект.Дата)=23) и (Минута(СсылкаНаОбъект.Дата)=59) и (Секунда(СсылкаНаОбъект.Дата)=59) Тогда
Иначе 
    СуммаДолга_ = СуммаДолга_ + СсылкаНаОбъект.СуммаДокумента; 
КонецЕсли;

В данном коде разработчик попытался учесть сумму создаваемого счета в общей сумме задолженности, если время документа отличается от конца дня. Однако счет на оплату сам по себе не делает движений по регистрам накопления и не формирует задолженность до момента отгрузки. Прибавляя СсылкаНаОбъект.СуммаДокумента к реальному остатку из регистра, мы получаем ложный долг даже у контрагента с нулевым сальдо.

Для устранения этой ошибки закомментируем или полностью удалим строку с прибавлением суммы счета:


// Получаем фактический остаток долга на момент формирования счета
СуммаДолга_ = ТекущийДолгПоКонтрагенту(СсылкаНаОбъект);

Причина 2. Логика проверки и вывод нулевого значения долга

Выясним причину, почему при отсутствии долга информационная область макета не выводится или отображается некорректно. В исходном коде вывод секции макета ограничен строгим условием:


Если СуммаДолга_ > 0 Тогда 
    ОбластьМакета = Макет.ПолучитьОбласть("СуммаДолга"); 
    ОбластьМакета.Параметры.Текст_Дата = "на дату " + Строка(Формат(СсылкаНаОбъект.Дата, "ДЛФ=Д")) + " составляет:"; 
    ОбластьМакета.Параметры.Текст_руб = Строка(СуммаДолга_) + " руб."; 
    ТабДокумент.Вывести(ОбластьМакета); 
КонецЕсли;

Если задолженность равна нулю, условие СуммаДолга_ > 0 не выполняется, и блок с информацией о взаиморасчетах полностью пропускается. Если бизнес-требования предполагают обязательный вывод надписи «0 руб.» при отсутствии задолженности, снимем условие или скорректируем обработку вывода:


ОбластьМакета = Макет.ПолучитьОбласть("СуммаДолга");
ОбластьМакета.Параметры.Текст_Дата = "на дату " + Строка(Формат(СсылкаНаОбъект.Дата, "ДЛФ=Д")) + " составляет:";
ОбластьМакета.Параметры.Текст_руб = Формат(СуммаДолга_, "ЧДЦ=2; ЧН='0,00 руб.'") + " руб.";
ТабДокумент.Вывести(ОбластьМакета);

Использование параметра ЧН (представление нулевого значения) в функции Формат() позволяет корректно форматировать нулевой остаток без написания дополнительных ветвлений Если ... Тогда.

Причина 3. Ошибки в запросе функции получения остатка

Рассмотрим функцию ТекущийДолгПоКонтрагенту. В исходной реализации запрос к регистру накопления содержал грубые методологические ошибки платформы 1С:


Функция ТекущийДолгПоКонтрагенту(СсылкаДокумент) Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ
    |    ВзаиморасчетыСКонтрагентамиОстатки.Организация,
    |    ВзаиморасчетыСКонтрагентамиОстатки.Контрагент,
    |    ВзаиморасчетыСКонтрагентамиОстатки.СуммаВзаиморасчетовОстаток КАК СуммаВзаиморасчетовОстаток
    |ИЗ
    |    РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки КАК ВзаиморасчетыСКонтрагентамиОстатки
    |ГДЕ
    |    ВзаиморасчетыСКонтрагентамиОстатки.Организация = &Организация
    |    И ВзаиморасчетыСКонтрагентамиОстатки.Контрагент = &Контрагент";
    
    Запрос.УстановитьПараметр("Контрагент", СсылкаДокумент.Контрагент);
    Запрос.УстановитьПараметр("Организация", СсылкаДокумент.Организация);
    
    Результат = Запрос.Выполнить();
    ВыборкаДетальныеЗаписи = Результат.Выбрать();
    ТекущийДолг = 0;
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        ТекущийДолг = ТекущийДолг + ВыборкаДетальныеЗаписи.СуммаВзаиморасчетовОстаток;
    КонецЦикла;
    
    Возврат ТекущийДолг;
КонецФункции

Проанализируем основные проблемы данного запроса:

  1. Отбор в предложении ГДЕ вместо параметров виртуальной таблицы: При обращении к виртуальной таблице РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки условия по измерениям необходимо обязательно передавать во второй параметр таблицы. Использование предложения ГДЕ заставляет СУБД сначала рассчитать итоги по всей таблице регистра и лишь затем фильтровать данные, что приводит к падению производительности.
  2. Отсутствие момента времени: В первый параметр виртуальной таблицы не передан период. В этом случае расчет выполняется на текущую дату выполнения сеанса, а не на дату выписки документа. Чтобы получить остаток строго на момент времени документа, необходимо использовать объект Граница.
  3. Отсутствие разреза по договору: В типовых конфигурациях взаиморасчеты ведутся в разрезе договоров. Если не накладывать отбор по договору контрагента, остатки суммируются по всем договорам (включая договоры поставки и комиссии), что искажает реальное сальдо по текущей сделке.
  4. Обход выборки вместо агрегатной функции: Нет необходимости обходить записи в цикле — достаточно применить агрегатную функцию СУММА(ЕСТЬNULL(...)).

Оптимальный вариант реализации функции расчета долга

С учетом всех правил разработки на платформе 1С перепишем функцию расчета остатка задолженности:


Функция ТекущийДолгПоКонтрагенту(СсылкаДокумент) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЕСТЬNULL(СУММА(ВзаиморасчетыОстатки.СуммаВзаиморасчетовОстаток), 0) КАК СуммаДолга
        |ИЗ
        |   РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(
        |       &Период,
        |       Организация = &Организация
        |           И Контрагент = &Контрагент
        |           И ДоговорКонтрагента = &ДоговорКонтрагента) КАК ВзаиморасчетыОстатки";
    
    // Передаем границу момента времени документа, исключая движения самого документа
    ГраницаВремени = Новый Граница(СсылкаДокумент.МоментВремени(), ВидГраницы.Исключая);
    
    Запрос.УстановитьПараметр("Период", ГраницаВремени);
    Запрос.УстановитьПараметр("Контрагент", СсылкаДокумент.Контрагент);
    Запрос.УстановитьПараметр("Организация", СсылкаДокумент.Организация);
    Запрос.УстановитьПараметр("ДоговорКонтрагента", СсылкаДокумент.ДоговорКонтрагента);
    
    Результат = Запрос.Выполнить();
    Если Не Результат.Пустой() Тогда
        Выборка = Результат.Выбрать();
        Выборка.Следующий();
        Возврат Выборка.СуммаДолга;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Такой подход гарантирует быстрое выполнение запроса через индексы виртуальной таблицы, точное совпадение остатков с отчетом «Ведомость по взаиморасчетам с контрагентами» на дату документа и исключает появление фиктивной задолженности.

← На главную