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