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