Как правильно реализовать проверку отрицательных остатков по «новой методике» в 1С 8.3

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

В современной практике разработки на платформе 1С:Предприятие 8.3 существует два основных подхода к проверке остатков при проведении документов. Традиционный («старый») метод подразумевает сначала проверку наличия товара на складе, а затем формирование движений. Однако более эффективным и производительным считается «новый» метод: система сначала записывает движения документа в базу данных, а затем одним запросом проверяет, не ушли ли остатки в минус. Рассмотрим подробно, как реализовать этот механизм, разберем типичные ошибки и выясним, почему остатки могут «удваиваться» при неправильном коде.

Суть «новой методики» и её преимущества

При использовании новой методики мы сознательно разрешаем документу сделать движения «вслепую». Главное преимущество здесь — повышение параллельности работы пользователей. В управляемом режиме блокировок система накладывает блокировку на записи регистра только в момент записи движений. Если мы сначала проверяем остатки запросом, нам приходится накладывать исключительную блокировку на остатки «авансом», что замедляет работу других пользователей. Рассмотрим реализацию по шагам.

Шаг 1: Подготовка данных документа во временной таблице

Для начала проанализируем данные, которые документ собирается списать. Нам необходимо сгруппировать строки табличной части по всем измерениям, которые присутствуют в регистре накопления (например, Номенклатура, Цена, Склад). Это важно, так как одна и та же номенклатура может встречаться в документе в нескольких строках с разной ценой или характеристиками.


Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	АктЗИП.Номенклатура КАК Номенклатура,
    |	АктЗИП.ЦенаЗакупки КАК Цена,
    |	АктЗИП.СчетЗакупа КАК СредстваЗакупки,
    |	СУММА(АктЗИП.Количество) КАК Количество
    |ПОМЕСТИТЬ ДокТЧ
    |ИЗ
    |	Документ.АктВыполненыхРабот.ЗИП КАК АктЗИП
    |ГДЕ
    |	АктЗИП.Ссылка = &Ссылка
    |ГРУППИРОВАТЬ ПО
    |	АктЗИП.Номенклатура,
    |	АктЗИП.ЦенаЗакупки,
    |	АктЗИП.СчетЗакупа";

Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();

Важный момент: Мы помещаем данные во временную таблицу ДокТЧ. Это позволит нам во втором запросе (после записи движений) обращаться только к тем позициям номенклатуры, которые изменились этим документом, а не проверять весь регистр целиком.

Шаг 2: Формирование движений и физическая запись

После подготовки данных мы формируем движения в регистр накопления. Обратите внимание, что в «новой методике» необходимо явно вызвать метод Движения.Записать() внутри процедуры ОбработкаПроведения.


// Формируем движения на основании выборки из первого запроса
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход();
    Движение.Период = Дата;
    Движение.Номенклатура = Выборка.Номенклатура;
    Движение.Цена = Выборка.Цена;
    Движение.СредстваЗакупки = Выборка.СредстваЗакупки;
    Движение.Количество = Выборка.Количество;
КонецЦикла;

// Устанавливаем флаг и записываем движения в базу данных
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Движения.Записать();

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

Шаг 3: Проверка отрицательных остатков

Выполняем второй запрос к виртуальной таблице Остатки. Здесь кроется критическая ошибка многих начинающих — получение остатков «на сейчас» без учета момента времени документа. Для корректной проверки нужно использовать объект Граница.


// Используем Границу Включая, чтобы увидеть итоги с учетом текущего документа
ПараметрГраница = Новый Граница(МоментВремени(), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Период", ПараметрГраница);

Запрос.Текст = 
    "ВЫБРАТЬ
    |	Остатки.Номенклатура,
    |	Остатки.КоличествоОстаток КАК Остаток
    |ИЗ
    |	РегистрНакопления.ОстаткиНоменклатуры.Остатки(&Период, Номенклатура В (ВЫБРАТЬ ДокТЧ.Номенклатура ИЗ ДокТЧ)) КАК Остатки
    |ГДЕ
    |	Остатки.КоличествоОстаток < 0";

РезультатПроверки = Запрос.Выполнить();
Если Не РезультатПроверки.Пустой() Тогда
    ВыборкаОшибок = РезультатПроверки.Выбрать();
    Пока ВыборкаОшибок.Следующий() Цикл
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Недостаточно товара: " + ВыборкаОшибок.Номенклатура + ". Дефицит: " + (-ВыборкаОшибок.Остаток);
        Сообщение.Сообщить();
        Отказ = Истина;
    КонецЦикла;
КонецЕсли;

Почему остатки «удваиваются» и уходят в глубокий минус?

Рассмотрим ситуацию, описанную в Сообщении 7 темы форума: пользователь видит, что при нехватке товара остаток становится странным (был 5, списали 6, стало -7). Это происходит по двум причинам:

  1. Отсутствие флага Отказ: Если запрос нашел отрицательный остаток, но вы не установили Отказ = Истина, система зафиксирует транзакцию. При следующем перепроведении новые движения добавятся к старым, если не очищать набор записей.
  2. Автоматическое удаление движений: В свойствах документа на закладке «Движения» параметр «Удаление движений» по умолчанию стоит в значении Удалять автоматически при перепроведении. Если вы используете Движения.Записать() вручную, убедитесь, что логика очистки старых записей синхронизирована с этим свойством.

Важные рекомендации по оптимизации

Проанализируем еще несколько нюансов, которые помогут сделать ваш код профессиональным:

Подводя итог, отметим: «новая методика» — мощный инструмент для ускорения работы 1С. Основной секрет её успеха заключается в правильном использовании временных таблиц, передаче корректного МоментаВремени в виртуальную таблицу остатков и обязательном использовании Отказ = Истина при обнаружении дефицита. Таким образом, транзакция откатится, и некорректные данные не попадут в базу.

← На главную