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