← Назад к вопросам
Как организовать контроль остатков при проведении документа?
2.0 Middle🔥 201 комментариев
#Стандарты разработки
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Контроль остатков при проведении документа
Это важнейшая задача в учёте: убедиться, что при проведении документа (например, отпуска товара) остатки не уйдут в минус.
Архитектура решения
1. Регистр остатков
// РегистрНакопления.ОстаткиТоваров
// ОСТАТКИ (не ОСТАТКИ И ОБОРОТЫ)
//
// Измерения:
// - Товар
// - Склад
//
// Ресурсы:
// - КоличествоОстаток (число)
2. Проверка перед проведением документа
// Документ.РасходнаяНакладная
&НаСервере
Процедура ПроверитьОстаткиТоваров()
// Проходим по каждой строке документа
Для Каждого СтрокаТовара Из Товары Цикл
// Получаем текущий остаток на складе
Остаток = ПолучитьОстатокТовара(
СтрокаТовара.Товар,
СтрокаТовара.Склад
);
// Проверяем: достаточно ли товара
Если Остаток < СтрокаТовара.Количество Тогда
ВызватьИсключение(СтрокаТовара.Товар.Наименование +
" на складе " + СтрокаТовара.Склад.Наименование +
": остаток только " + Остаток +
" шт., а вы хотите отпустить " + СтрокаТовара.Количество + " шт.");
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Функция ПолучитьОстатокТовара(Товар, Склад)
Запрос = Новый Запрос();
Запрос.Текст =
"ВЫБРАТЬ
| СУММА(ОстаткиТоваров.КоличествоОстаток) КАК Остаток
|ИЗ РегистрНакопления.ОстаткиТоваров КАК ОстаткиТоваров
|ГДЕ ОстаткиТоваров.Товар = &Товар
| И ОстаткиТоваров.Склад = &Склад";
Запрос.УстановитьПараметр("Товар", Товар);
Запрос.УстановитьПараметр("Склад", Склад);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Остаток;
Иначе
Возврат 0;
КонецЕсли;
КонецФункции
Проверка в процедуре проведения
&НаСервере
Процедура ПроведениеПроведение(Отказ, Режим)
НачатьТранзакцию();
Попытка
// ШАГ 1: Проверяем остатки
ПроверитьОстаткиТоваров();
// ШАГ 2: Создаём движения по регистру
СоздатьДвиженияРегистров();
// ШАГ 3: Записываем изменения
ЗаписьДвижений();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Отказ = Истина;
// Показываем пользователю, в чём проблема
Сообщить("Ошибка при проведении: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Использование блокировок для параллелизма
Если несколько пользователей одновременно проводят документы, может быть проблема race condition. Решение — блокировки:
&НаСервере
Процедура ПроведениеПроведение(Отказ, Режим)
НачатьТранзакцию();
Попытка
// БЛОКИРУЕМ все товары из документа
// Это предотвращает race condition
ТекстЗапроса =
"ВЫБРАТЬ
| Товары.Товар
|ИЗ Документ.РасходнаяНакладная.Товары КАК Товары
|ГДЕ Товары.Ссылка = &Документ
| ДЛЯ ОБНОВЛЕНИЯ";
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Документ", Ссылка);
Запрос.Выполнить();
// Теперь проверяем остатки
ПроверитьОстаткиТоваров();
// Создаём движения
СоздатьДвиженияРегистров();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Отказ = Истина;
Сообщить("Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Что делает FOR UPDATE:
- Получает блокировку на таблице товаров
- Другие транзакции ждут, пока эта завершится
- Предотвращает одновременные изменения
- Гарантирует консистентность данных
Создание движений регистра
Процедура СоздатьДвиженияРегистров()
// Получаем текущие движения
Движения.ОстаткиТоваров.Записать();
// (они уже заполнены из табличной части документа)
// Для документа "РасходнаяНакладная"
// в табличной части создаём движения ОСТАТОК со знаком минус
Для Каждого Строка Из Товары Цикл
НовоеДвижение = Движения.ОстаткиТоваров.ДобавитьОстаток();
НовоеДвижение.Товар = Строка.Товар;
НовоеДвижение.Склад = Строка.Склад;
НовоеДвижение.Количество = -Строка.Количество; // МИНУС!
КонецЦикла;
КонецПроцедуры
Альтернатива: свойство "Контролировать остатки"
В 1С есть встроенный механизм в РегистрНакопления:
// При создании РегистрНакопления в конфигурации:
// Свойство "Контролировать остатки" = Истина
//
// Тогда 1С автоматически будет проверять,
// что остаток НЕ уходит в минус при проведении
// Но это работает ТОЛЬКО если:
// - Регистр с типом "ОСТАТКИ"
// - Свойство включено
// - Документ использует встроенные движения
Обработка ошибок
// Что сделать, если остатков недостаточно?
Процедура ПроверитьОстаткиТоваровСОбработкой()
ТоварыСПроблемами = Новый Массив();
Для Каждого СтрокаТовара Из Товары Цикл
Остаток = ПолучитьОстатокТовара(СтрокаТовара.Товар, СтрокаТовара.Склад);
Если Остаток < СтрокаТовара.Количество Тогда
// Вариант 1: Строгая проверка — ошибка
Если НЕ РазрешитьОтрицательныеОстатки Тогда
ВызватьИсключение "Недостаточно остатков";
КонецЕсли;
// Вариант 2: Предупреждение
ТоварыСПроблемами.Добавить(СтрокаТовара);
КонецЕсли;
КонецЦикла;
Если ТоварыСПроблемами.Количество() > 0 Тогда
// Спрашиваем пользователя
Ответ = Вопрос(
"Недостаточно остатков для некоторых товаров. Продолжить?",
РежимДиалогаВопрос.ДаНет
);
Если Ответ = НЕ ДаНет.Да Тогда
ВызватьИсключение "Проведение отменено пользователем";
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Полный пример: РасходнаяНакладная
// Документ.РасходнаяНакладная
&НаСервере
Процедура ПроведениеПроведение(Отказ, Режим)
НачатьТранзакцию();
Попытка
// 1. Блокируем товары
БлокировкаТовара = Новый БлокировкаДанных();
БлокировкаТовара.Добавить("Документ.РасходнаяНакладная.Товары", "Ссылка", Ссылка);
БлокировкаТовара.Заблокировать();
// 2. Проверяем остатки
Для Каждого Строка Из Товары Цикл
Остаток = ПолучитьОстаток(Строка.Товар, Строка.Склад);
Если Остаток < Строка.Количество Тогда
Отказ = Истина;
Сообщить("Недостаточно товара: " + Строка.Товар.Наименование);
Возврат;
КонецЕсли;
// 3. Создаём движение (минус остаток)
Движение = Движения.ОстаткиТоваров.ДобавитьОстаток();
Движение.Товар = Строка.Товар;
Движение.Склад = Строка.Склад;
Движение.Количество = -Строка.Количество;
КонецЦикла;
// 4. Записываем движения
Движения.ОстаткиТоваров.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Отказ = Истина;
КонецПопытки;
КонецПроцедуры
Ключевые моменты
- Всегда проверяй остатки перед проведением
- Используй транзакции для атомарности
- Блокируй данные чтобы избежать race conditions
- Создавай движения со знаком минус для расхода
- Обрабатывай ошибки информативно
- Логируй проблемы для аудита
Это гарантирует, что ваша система всегда содержит корректные данные об остатках!