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