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