Как правильно реализовать проведение документа по нескольким регистрам накопления с контролем остатков в 1С

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

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

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

Разбор архитектуры решения: конструктор движений и ручная доработка

Начнем с того, что для первичного формирования движений мы действительно можем использовать Конструктор движений. Он позволяет быстро сопоставить реквизиты документа с измерениями и ресурсами регистра. Рассмотрим ситуацию, когда у нас есть:

  1. ОстаткиПриродыСырьё (Регистр накопления, тип «Остатки») — здесь мы отражаем расход компонентов.
  2. ПриродаНомер (Регистр накопления, тип «Остатки» или «Обороты») — здесь фиксируется приход (присвоение) готовых номеров.
  3. ИсторияНомеров (Регистр сведений) — здесь хранится дополнительная информация.

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

Реализация современной методики контроля остатков

Проанализируем так называемую «новую методику» проведения. В отличие от старого подхода, где мы сначала проверяем наличие товара, а потом записываем расход, новая методика работает «от обратного». Это позволяет избежать блокировок и ускоряет работу. Разберем алгоритм по шагам:

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

Рассмотрим пример кода, реализующий эту логику в модуле документа:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Очищаем старые движения (если документ перепроводится)
    Движения.ОстаткиПриродыСырьё.Записывать = Истина;
    Движения.ПриродаНомер.Записывать = Истина;
    Движения.ИсторияНомеров.Записывать = Истина;
    
    Движения.Записать();
    
    // 2. Устанавливаем блокировку данных для контроля остатков
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиПриродыСырьё");
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    ЭлементБлокировки.ИсточникДанных = СписокСырьяДляПроверки; // ТЗ или результат запроса
    ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
    Блокировка.Заблокировать();

    // 3. Выполняем проверку отрицательных остатков запросом
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Остатки.Номенклатура КАК Номенклатура,
        |	Остатки.КоличествоОстаток КАК Остаток
        |ИЗ
        |	РегистрНакопления.ОстаткиПриродыСырьё.Остатки(&МоментВремени, Номенклатура В (&СписокНоменклатуры)) КАК Остатки
        |ГДЕ
        |	Остатки.КоличествоОстаток < 0";

    Запрос.УстановитьПараметр("МоментВремени", Граница);
    Запрос.УстановитьПараметр("СписокНоменклатуры", СписокНоменклатурыИзДокумента);
    
    РезультатЗапроса = Запрос.Выполнить();
    Если Не РезультатЗапроса.Пустой() Тогда
        Отказ = Истина;
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Недостаточно сырья: " + Выборка.Номенклатура + ". Дефицит: " + (-Выборка.Остаток);
            Сообщение.Сообщить();
        КонецЦикла;
    КонецЕсли;

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

Работа с управляемыми блокировками

Выясним, почему важно использовать объект БлокировкаДанных. При проведении по нескольким регистрам (особенно когда задействован регистр ОстаткиПриродыСырьё), критично предотвратить ситуацию «двойного списания». Если два пользователя одновременно проводят документ по одной и той же позиции, без блокировок оба могут увидеть положительный остаток и успешно провести документ, в итоге остаток станет отрицательным. Использование исключительной блокировки перед проверкой гарантирует, что второй пользователь подождет, пока первый завершит свою транзакцию.

Особенности записи в Регистр сведений (РС)

Проанализируем работу с Регистром сведений. В вашей задаче упоминалось ведение информации в РС. Здесь есть два пути:

  1. Подчиненный регистратору: Если в свойствах регистра сведений указан режим записи «Подчинение регистратору», то вы работаете с ним так же, как с регистром накопления — через коллекцию Движения.ИсторияНомеров. Это идеальный вариант для документов: если проведение документа отменится из-за нехватки сырья, записи в РС удалятся автоматически.
  2. Независимый: Если регистр независимый, вам придется вручную создавать НаборЗаписей. Мы рекомендуем использовать подчиненный регистр для задач, жестко связанных с проведением документа.

Оптимизация: использование временных таблиц

Разберем, как сделать код еще более профессиональным. Вместо того чтобы передавать в параметры запроса простые списки, лучше использовать Временные таблицы (ВТ). Мы можем поместить данные из табличной части документа во временную таблицу в начале процедуры. Это позволит:

Контроль корректности данных документа

Важно помнить о проверке входных данных. Перед тем как данные попадут в регистры, необходимо убедиться, что пользователь не указал отрицательное количество в самом документе. Конструктор движений не проверяет знак числа, и если в документе будет стоять «-10», то при «расходе» система фактически сделает «приход», что исказит учет. Поэтому всегда добавляйте проверку Количество > 0 перед началом формирования движений.

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

← На главную