Почему не срабатывает проверка отрицательных остатков при проведении документа в 1С?

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

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

Анализ механизма записи движений

Для начала проанализируем, как платформа взаимодействует с регистрами. Когда мы пишем в коде Движения.ОстаткиМатериалов.Записывать = Истина, мы всего лишь выставляем флаг для платформы. Этот флаг указывает, что по завершении процедуры ОбработкаПроведения (если не возникло ошибок и Отказ остался равным Ложь), набор записей должен быть автоматически сброшен в базу данных. Рассмотрим ситуацию: внутри процедуры мы добавили строки в коллекцию движений, но физически в таблицах базы данных их еще нет. Следовательно, запрос к виртуальной таблице Остатки, выполненный в этой же процедуре, их «не увидит».

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

Разберем проблему с принудительной записью

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


// Запишем пустые наборы записей, чтобы читать остатки без учета данных в документе
Движения.ОстаткиМатериалов.Записать(); 

// ... далее идет логика формирования движений (заполнение коллекции Движения.ОстаткиМатериалов) ...

// Проверка отрицательных остатков
Запрос3 = Новый Запрос;
// ... текст запроса к РегистрНакопления.ОстаткиМатериалов.Остатки ...
РезультатЗапроса = Запрос3.Выполнить();

В этом сценарии метод Записать() вызывается в самом начале. Он очищает движения документа в базе. Затем программист наполняет коллекцию Движения.ОстаткиМатериалов новыми данными. Но перед выполнением запроса Запрос3 повторный вызов Записать() отсутствует! В результате запрос обращается к базе данных, где движения этого документа все еще «пустые». Именно поэтому проверка не находит отрицательных остатков.

Правильная реализация «Новой методики» проведения

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

  1. Устанавливаем режим записи движений.
  2. Формируем движения документа (наполняем коллекции).
  3. Принудительно записываем движения в базу данных внутри транзакции.
  4. Выполняем запрос к таблице остатков, который теперь видит только что записанные данные.
  5. Если обнаруживаем отрицательный остаток — устанавливаем Отказ = Истина.

Посмотрим на пример реализации:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)

    // 1. Подготовительные действия
    Движения.ОстаткиМатериалов.Записывать = Истина;
    
    // Если нужно очистить старые движения (важно для перепроведения)
    Движения.ОстаткиМатериалов.Записать();

    // 2. Формирование движений
    Для Каждого ТекСтрокаТовары Из Товары Цикл
        Движение = Движения.ОстаткиМатериалов.ДобавитьРасход();
        Движение.Период = Дата;
        Движение.Материал = ТекСтрокаТовары.Номенклатура;
        Движение.Склад = Склад;
        Движение.Количество = ТекСтрокаТовары.Количество;
    КонецЦикла;

    // 3. ФИЗИЧЕСКАЯ ЗАПИСЬ движений для контроля
    // Без этой строки запрос в пункте 4 не увидит расход текущего документа!
    Движения.ОстаткиМатериалов.Записать();

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

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

Нюансы работы с Моментом Времени

Важно обратить внимание на параметр запроса &МоментВремени. При использовании «новой методики» (после записи движений), мы должны убедиться, что запрос получает остатки именно на позицию текущего документа, включая его самого. Использование объекта Граница с видом ВидГраницы.Включая гарантирует, что наши только что записанные движения будут учтены в расчете виртуальной таблицы остатков.

Почему это не «ошибка в книге Радченко»?

Посмотрим на ситуацию критически. В учебных пособиях часто приводятся фрагменты кода, которые решают узкую задачу. В одном месте может рассматриваться расчет себестоимости, в другом — контроль остатков. При объединении этих кусков кода программист должен понимать логику работы СУБД и транзакционного механизма 1С. Если вы вызываете Записать() в начале процедуры для очистки записей, платформа считает, что текущее состояние регистра в базе данных — «пусто». Любые последующие изменения в коллекции Движения останутся в памяти до тех пор, пока вы снова не вызовете Записать() или пока процедура не завершится.

Управляемые блокировки: необходимый шаг

Проанализируем еще один важный аспект, который часто упускается. Контроль остатков (любой методикой) будет работать некорректно в многопользовательской среде без использования управляемых блокировок. Если два пользователя одновременно проводят документ по одному и тому же товару, может возникнуть ситуация «гонки»: оба запроса увидят наличие товара, оба документа проведутся, и остаток уйдет в минус. Чтобы этого избежать, перед запросом остатков необходимо заблокировать данные.

Пример установки блокировки:


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

Эту конструкцию следует размещать до выполнения запроса на проверку остатков. В «новой» методике блокировка обычно ставится перед первым вызовом Движения.Записать() или непосредственно перед запросом, чтобы гарантировать неизменность остатков другими транзакциями до завершения нашей операции.

Итоги и рекомендации

Подведем итог нашему разбору. Если вы столкнулись с тем, что отрицательные остатки не определяются внутри процедуры проведения, проверьте следующие моменты:

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

← На главную