Связь Заказов и Реализации при контроле согласованной суммы лимита в 1С

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

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

В этой статье мы подробно разберем, почему прямой запрос к табличным частям документов при проведении — это архитектурная ошибка, и предложим два надежных решения для реализации этого механизма.

---

Почему прямой запрос по документам в транзакции проведения — это антипаттерн?

Посмотрим на код, который часто пытаются использовать начинающие разработчики (как в Сообщении 13 и 27 темы). Они собирают все проведенные заказы за период, левым соединением подключают к ним реализации по измерению Сделка и пытаются рассчитать некую «чистую» сумму расхода лимита. Выясним, почему этот подход неработоспособен в реальной базе данных:

  1. Проблема дублирования и дробления сумм. Если один заказ отгружается несколькими реализациями (или наоборот, одна реализация оформлена по нескольким заказам), левое соединение в запросе начинает размножать строки ТЧ. Суммы документов начинают двоиться или троиться. Написание «универсального» запроса, который корректно учтет все варианты частичной отгрузки, возвратов и корректировок по цепочке документов, превращается в громоздкую математическую задачу, которая будет выполняться непозволительно долго.
  2. Низкая производительность. Обращение к физическим таблицам документов (особенно к их табличным частям Товары) при проведении каждого нового документа заставляет СУБД выполнять тяжелые операции сканирования таблиц. С ростом базы данных проведение документов начнет зависать.
  3. Отсутствие транзакционной безопасности (Race Condition). Если два менеджера одновременно проводят два разных заказа по одному контрагенту, их транзакции параллельно прочитают данные из таблиц документов. Ни один из них еще не увидит документ соседа, так как они еще не зафиксированы в базе. В результате оба документа проведутся, и лимит будет превышен.

Для решения этой задачи необходимо использовать специализированные регистры накопления и проверенные архитектурные паттерны 1С.

---

Решение №1: Классическая формула баланса лимитов (без прямого связывания документов)

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

Доступный лимит = Разрешенная сумма - Текущий долг по отгрузкам - Сумма неотгруженных заказов

Рассмотрим, как меняются показатели при движении по цепочке документов:

Как видим, итоговая сумма доступного лимита осталась абсолютно корректной, при этом нам не потребовалось связывать документы в запросе! Мы просто получили остатки по двум независимым регистрам: регистру взаиморасчетов (фактический долг) и регистру заказов (остаток неотгруженного по заказам).

---

Решение №2: Использование специализированного регистра накопления «Лимиты отгрузки»

Если бизнес-требования диктуют необходимость жесткого контроля именно в разрезе выданных разрешений (документов «Согласованная сумма»), то наиболее правильным решением будет создание нового регистра накопления остатков. Разберем по шагам, как его спроектировать и использовать.

Шаг 1. Создание регистра накопления

Создадим регистр накопления ЛимитыОтгрузки с видом Остатки.

Определим следующие свойства регистра:

Шаг 2. Логика движений документов

Настроим проведение документов таким образом, чтобы они изменяли остаток нашего лимита в регистре:

  1. Документ «Согласованная сумма» делает движение Приход в регистр ЛимитыОтгрузки на согласованную сумму по конкретному контрагенту.
  2. Документ «Заказ покупателя» делает движение Расход из регистра ЛимитыОтгрузки на сумму заказа. При проведении обязательно выполняется проверка на то, чтобы остаток по регистру не ушел в минус.
  3. Документ «Реализация товаров и услуг», если он оформлен на основании заказа, делает два движения в рамках одной транзакции:
    • Движение Приход (возврат резерва лимита по заказу) на сумму отгружаемого заказа.
    • Движение Расход (фактическое списание лимита) на сумму самой реализации.
    Если реализация оформляется без заказа, она делает только движение Расход.

При такой схеме движений остаток по регистру ЛимитыОтгрузки всегда отражает актуальный свободный лимит контрагента.

---

Программная реализация контроля лимитов с транзакционной блокировкой

Проанализируем программный код, который необходимо разместить в модуле проведения документов «Заказ покупателя» и «Реализация товаров и услуг» для проверки остатка лимита. Чтобы избежать параллельного проведения документов с обходом лимита, мы обязательно должны использовать управляемые блокировки данных.

Разберем по шагам пример функции проверки лимита в модуле проведения документа:


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

КонецПроцедуры

---

Рекомендации по организации смежных бизнес-процессов

Для того чтобы предложенная техническая схема работала без сбоев на уровне реальных продаж предприятия, рекомендуем внедрить следующие организационные правила:

  1. Запрет на создание Реализации (РТУ) без Заказа покупателя. Если менеджеры смогут отгружать товары напрямую через РТУ в обход предварительных заказов, система контроля лимитов потеряет смысл. РТУ должна создаваться строго на основании ранее утвержденного и прошедшего проверку Заказа. Это гарантирует, что резервирование лимита произойдет еще на этапе планирования сделки.
  2. Внедрение механизма закрытия заказов. Бывают ситуации, когда заказ был проведен (и заблокировал часть лимита), но клиент отказался от сделки. Чтобы эти «зависшие» суммы не блокировали лимиты контрагента вечно, необходимо регулярно использовать документ ЗакрытиеЗаказовПокупателей. При его проведении система должна делать приходные (корректирующие) движения по нашему регистру лимитов, возвращая неиспользованные суммы обратно в оборот.
  3. Запрет на редактирование проведенных заказов. Если заказ уже прошел проверку согласованной суммы и был проведен, любые изменения в его товарной или ценовой части должны автоматически отправлять документ на повторную проверку лимитов.

Применение регистрового подхода совместно с управляемыми блокировками данных позволит создать быструю, надежную и масштабируемую систему контроля лимитов отгрузки в конфигурациях 1С на обычных формах.

← На главную