Как работают блокировки и контроль остатков при перепроведении документа с удалением строк в 1С?

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

При разработке механизмов проведения документов с контролем остатков нередко возникает вопрос: что происходит при редактировании и перепроведении документа, когда из новой версии табличной части удаляются ранее списанные номенклатурные позиции? Разберем подробно, требуется ли дополнительно блокировать удаленные позиции для защиты от нежелательного списания другими транзакциями и как платформа 1С совместно с СУБД гарантирует целостность данных при возможном откате транзакции.

Теоретические основы изоляции транзакций и разделения итогов

Проанализируем механику работы СУБД и сервера 1С при выполнении транзакции проведения документа:

  1. Изоляция на уровне СУБД (Read Committed / MVCC). При отмене проведения или очистке старых движений удаляемый расход освобождает товарные остатки только внутри текущей транзакции. Другие параллельные транзакции не увидят освободившийся остаток до тех пор, пока текущая транзакция не будет успешно зафиксирована (Commit). Если проведение завершится ошибкой и произойдет откат (Rollback), СУБД вернет строки таблицы регистра в исходное состояние. Параллельные транзакции физически не могут прочитать «грязные» данные и списать товар, освобождение которого еще не зафиксировано.
  2. Механизм разделения итогов. Если для регистра накопления включено разделение итогов, параллельные сеансы могут одновременно записывать движения по одним и тем же аналитическим разрезам (номенклатуре, складу), используя системный разделитель. Это повышает параллельность записи, но переносит задачу контроля бизнес-логики и предотвращения ухода остатков в минус на уровень управляемых блокировок платформы 1С.
  3. Поведение свойства БлокироватьДляИзменения. Установка данного свойства в значение Истина для набора записей регистра накладывает исключительную управляемую блокировку на пространстве данных сервера 1С. Важно понимать, что блокировка накладывается строго по тем комбинациям измерений, которые содержатся в текущем наборе записей в памяти. Позиции, удаленные из документа, этим свойством не блокируются, но для них блокировка со стороны текущего документа и не требуется, так как их списание уменьшается или аннулируется.

Методика контроля остатков после записи движений

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

Посмотрим на пример реализации обработчика проведения документа:


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

Явная установка управляемой блокировки через объект БлокировкаДанных

Если бизнес-процесс требует заблокировать как новые, так и ранее списанные позиции (например, при расчете себестоимости или сложных взаимосвязанных списаниях), разберем по шагам, как наложить управляемую блокировку программно:

  1. В свойствах документа устанавливаем свойство Удаление движений в значение Не удалять автоматически.
  2. В начале процедуры проведения считываем старые движения набора записей с помощью метода Прочитать() или отдельного запроса к регистру.
  3. Формируем общий список номенклатуры, объединяя строки текущей табличной части документа и прочитанные старые записи.
  4. Создаем объект БлокировкаДанных, добавляем в него пространство блокировок для регистра накопления с режимом РежимБлокировкиДанных.Исключительный и вызываем метод Заблокировать().

Посмотрим на пример установки явной блокировки по объединенному списку:


// Считываем старые движения документа до их перезаписи
Движения.Товары.Прочитать();
ТаблицаСтарыхДвижений = Движения.Товары.Выгрузить();

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

Для Каждого СтрокаДвижения Из ТаблицаСтарыхДвижений Цикл
    Если СписокНоменклатуры.Найти(СтрокаДвижения.Номенклатура) = Неопределено Тогда
        СписокНоменклатуры.Добавить(СтрокаДвижения.Номенклатура);
    КонецЕсли;
КонецЦикла;

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

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

← На главную