При анализе взаиморасчетов с контрагентами в конфигурации «1С:Управление торговлей 10.3» часто возникает задача получить отчет, в котором для каждого случая появления дебиторской задолженности отображается парный документ ее закрытия, а также даты начала и окончания долга. Ситуация усложняется тем, что в договорах контрагентов не всегда включен признак ведения расчетов по документам расчетов, а типовой регистр накопления ВзаиморасчетыСКонтрагентами хранит сводные суммы движений.
Разберем по шагам, как решить эту задачу средствами языка запросов 1С, выясним причину сложностей стандартного подхода и рассмотрим альтернативные варианты реализации.
Первая интуитивная попытка большинства разработчиков заключается в выборке из виртуальной таблицы ОстаткиИОбороты регистра ВзаиморасчетыСКонтрагентами с периодичностью Регистратор. В ней пытаются поймать моменты, когда сальдо меняет знак:
Возникновение долга: конечный остаток больше нуля, а начальный был меньше либо равен нулю.
Погашение долга: конечный остаток стал меньше либо равен нулю, а начальный был строго больше нуля.
Однако если просто вывести такие строки в один список, даты возникновения и закрытия окажутся в разных строках таблицы. Попытка связать их в единую строку через простое объединение не дает нужного результата — требуется корректное соединение строк между собой.
Рассмотрим вариант реализации, предложенный в обсуждении. Логика решения строится на разделении потока движений на два независимых набора во временных таблицах с последующим сопоставлением по минимальной подходящей дате закрытия.
Разберем алгоритм по этапам:
Вт_Появление, отбирая строки, где начальный остаток был неположительным, а конечный стал положительным.Вт_Закрытие, где начальный остаток был больше нуля, а конечный стал меньше либо равен нулю.Вт_Появление.ДолгПоявился <= Вт_Закрытие.ДатаЗакрылся) и с помощью агрегатной функции МИНИМУМ() находим самую раннюю дату погашения, следующую за появлением долга.Посмотрим на пример текста запроса:
ВЫБРАТЬ
ВЫБОР
КОГДА Взаиморасчеты.СуммаВзаиморасчетовКонечныйОстаток > 0
И Взаиморасчеты.СуммаВзаиморасчетовНачальныйОстаток <= 0
ТОГДА Взаиморасчеты.Период
ИНАЧЕ 0
КОНЕЦ КАК ДолгПоявился,
ВЫБОР
КОГДА Взаиморасчеты.СуммаВзаиморасчетовКонечныйОстаток > 0
И Взаиморасчеты.СуммаВзаиморасчетовНачальныйОстаток <= 0
ТОГДА Взаиморасчеты.Регистратор
ИНАЧЕ НЕОПРЕДЕЛЕНО
КОНЕЦ КАК ДолгПоявился_Регистратор
ПОМЕСТИТЬ Вт_Появление
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.ОстаткиИОбороты(&Дата1, &Дата2, Регистратор, , ДоговорКонтрагента = &ДоговорКонтрагента) КАК Взаиморасчеты
ГДЕ
Взаиморасчеты.СуммаВзаиморасчетовКонечныйОстаток > 0
И Взаиморасчеты.СуммаВзаиморасчетовНачальныйОстаток <= 0
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВЫБОР
КОГДА Взаиморасчеты.СуммаВзаиморасчетовКонечныйОстаток <= 0
И Взаиморасчеты.СуммаВзаиморасчетовНачальныйОстаток > 0
ТОГДА Взаиморасчеты.Период
ИНАЧЕ 0
КОНЕЦ КАК ДатаЗакрылся,
ВЫБОР
КОГДА Взаиморасчеты.СуммаВзаиморасчетовКонечныйОстаток <= 0
И Взаиморасчеты.СуммаВзаиморасчетовНачальныйОстаток > 0
ТОГДА Взаиморасчеты.Регистратор
ИНАЧЕ НЕОПРЕДЕЛЕНО
КОНЕЦ КАК ЗакрылсяРегистратор
ПОМЕСТИТЬ Вт_Закрытие
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.ОстаткиИОбороты(&Дата1, &Дата2, Регистратор, , ДоговорКонтрагента = &ДоговорКонтрагента) КАК Взаиморасчеты
ГДЕ
Взаиморасчеты.СуммаВзаиморасчетовКонечныйОстаток <= 0
И Взаиморасчеты.СуммаВзаиморасчетовНачальныйОстаток > 0
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Вт_Появление.ДолгПоявился КАК ДолгПоявился_свзь,
Вт_Появление.ДолгПоявился_Регистратор,
МИНИМУМ(Вт_Закрытие.ДатаЗакрылся) КАК ДатаЗакрылся
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Вт_Появление КАК Вт_Появление
ПОЛНОЕ СОЕДИНЕНИЕ Вт_Закрытие КАК Вт_Закрытие
ПО Вт_Появление.ДолгПоявился <= Вт_Закрытие.ДатаЗакрылся
СГРУППИРОВАТЬ ПО
Вт_Появление.ДолгПоявился_Регистратор,
Вт_Появление.ДолгПоявился
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Данные.ДатаЗакрылся,
Вт_Закрытие.ЗакрылсяРегистратор,
ВТ_Данные.ДолгПоявился_Регистратор,
ВТ_Данные.ДолгПоявился_свзь
ПОМЕСТИТЬ ВТ_Буфер
ИЗ
Вт_Закрытие КАК Вт_Закрытие
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Данные КАК ВТ_Данные
ПО Вт_Закрытие.ДатаЗакрылся = ВТ_Данные.ДатаЗакрылся
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Вт_Появление.ДолгПоявился,
Вт_Появление.ДолгПоявился_Регистратор,
ВТ_Буфер.ДатаЗакрылся,
ВТ_Буфер.ЗакрылсяРегистратор
ИЗ
Вт_Появление КАК Вт_Появление
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Буфер КАК ВТ_Буфер
ПО Вт_Появление.ДолгПоявился = ВТ_Буфер.ДолгПоявился_свзь
И Вт_Появление.ДолгПоявился_Регистратор = ВТ_Буфер.ДолгПоявился_Регистратор
Проанализируем ограничения, с которыми можно столкнуться при использовании приведенного выше подхода:
ДатаПоявления <= ДатаЗакрытия с выбором минимума может привязать несколько документов возникновения к одному и тому же первому документу погашения. Кроме того, соединение по неравенству порождает декартово произведение, что на больших объемах данных существенно снижает производительность СУБД.&Дата1), а погашен внутри периода, документ возникновения окажется вне выборки.ДЕНЬ(Дата2) - ДЕНЬ(Дата1) нельзя, так как она возвращает лишь номер дня в месяце (от 1 до 31). Для корректного вычисления продолжительности долга в днях обязательно используйте функцию платформы РАЗНОСТЬДАТ(ДатаНачала, ДатаОкончания, ДЕНЬ).В языке запросов платформы 1С:Предприятие отсутствуют классические оконные функции. Поэтому расчет нарастающего итога и интервалов погашения исключительно в запросе получается громоздким. Если по договору ведется непрерывный документооборот с частичными закрытиями, наиболее надежным и быстрым способом является линейная обработка выборки на встроенном языке:
ВзаиморасчетыСКонтрагентами по договору, упорядоченные по моменту времени (период, регистратор, номер строки).Такой программный обход выполняется за один линейный проход по таблице, исключает блокировки базы данных и дает математически точное сопоставление дат возникновения и закрытия задолженности при любых графиках расчетов.