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