Как правильно реализовать контроль остатков при списании в 1С и исправить ошибки в коде?

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

При разработке механизмов списания товаров начинающие программисты 1С часто сталкиваются с ситуацией, когда алгоритм проверки остатков работает некорректно: либо выдает ошибку на любой товар, либо дублирует сообщения об отсутствии позиций столько раз, сколько строк находится в таблице остатков. Разберем подробно, почему так происходит, и рассмотрим правильные подходы к решению этой задачи.

Анализ типичной ошибки: почему код работает неверно

Рассмотрим ситуацию, представленную в исходном вопросе. Основная проблема заключалась в использовании вложенного цикла для сравнения каждой строки табличной части документа с каждой строкой выборки из остатков регистра. Проанализируем этот фрагмент:


Пока ВыборкаДетальныеЗаписи.Следующий Цикл
    Для каждого СтрокаТабЧасти Из СписаниеС Цикл
        Если СтрокаТабЧасти.СИЗ <> ВыборкаДетальныеЗаписи.СИЗ Тогда
            // Ошибка здесь!
            НоваяСтрока = ТаблицаЗначений.Добавить();
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Выясним причину некорректной работы. Если на складе есть 4 различных товара, а вы пытаетесь списать один товар, который присутствует на складе, цикл сравнит его с каждой из 4 позиций остатков. В трех случаях из четырех условие СтрокаТабЧасти.СИЗ <> ВыборкаДетальныеЗаписи.СИЗ будет истинным, и система выдаст сообщение об ошибке, хотя товар на самом деле есть. Именно поэтому автор вопроса получал ошибку 4 раза подряд.

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

Решение №1: Использование метода Найти() для таблицы значений

Самый простой способ исправить логику, не переписывая весь код кардинально, — это выгрузить результат запроса в таблицу значений и использовать встроенный метод поиска. Рассмотрим, как это сделать правильно:

  1. Выполним запрос к остаткам только по тем товарам, которые есть в документе (это важно для производительности).
  2. Выгрузим результат в ТаблицаОстатков.
  3. Обойдем циклом табличную часть документа и проверим наличие каждой позиции в таблице остатков.

// Подготовим список товаров из документа для запроса
СписокТоваров = СписаниеС.ВыгрузитьКолонку("СИЗ");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СкладСИЗОстатки.СИЗ КАК СИЗ
    |ИЗ
    |	РегистрНакопления.СкладСИЗ.Остатки(, СИЗ В (&СписокТоваров)) КАК СкладСИЗОстатки";

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

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

В этом примере мы используем метод Найти(), который возвращает строку таблицы или Неопределено. Это исключает дублирование сообщений и корректно обрабатывает ситуацию отсутствия товара.

Решение №2: "Старая" методика контроля (Проверка перед проведением)

Если нам нужно проверять не только наличие "в принципе", но и достаточное количество товара, лучше всего использовать запрос, который сразу сопоставит данные документа и данные регистра. Проанализируем этот подход по шагам:

  1. Сгруппируем строки табличной части документа, так как один и тот же товар может встречаться в разных строках.
  2. Передадим эту сгруппированную таблицу в запрос.
  3. В запросе выполним Левое Соединение данных документа с остатками регистра.

Пример кода для реализации такой проверки:


// 1. Подготовка данных документа
ТаблицаСписания = СписаниеС.Выгрузить();
ТаблицаСписания.Свернуть("СИЗ", "Количество"); // Суммируем количество по одинаковым СИЗ

// 2. Запрос для проверки
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Док.СИЗ КАК СИЗ,
    |	Док.Количество КАК Требуется,
    |	ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК ВНаличии
    |ПОМЕСТИТЬ ВТ_Документ
    |ИЗ
    |	&ТаблицаСписания КАК Док
    |;
    |
    |ВЫБРАТЬ
    |	ВТ.СИЗ,
    |	ВТ.Требуется,
    |	ВТ.ВНаличии
    |ИЗ
    |	ВТ_Документ КАК ВТ
    |ГДЕ
    |	ВТ.Требуется > ВТ.ВНаличии";

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

Если Не Результат.Пустой() Тогда
    Выборка = Результат.Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = СтрШаблон("Недостаточно товара %1. Требуется %2, в наличии %3", 
                            Выборка.СИЗ, Выборка.Требуется, Выборка.ВНаличии);
        Сообщение.Сообщить();
    КонецЦикла;
    Отказ = Истина;
КонецЕсли;

Этот метод является более профессиональным, так как он за один вызов сервера проверяет все позиции и сразу находит разницу между требуемым и имеющимся количеством.

Решение №3: "Новая" методика (Пост-контроль)

В современных конфигурациях 1С (начиная с платформы 8.2/8.3) рекомендуется использовать методику контроля остатков после записи движений. Разберем, в чем ее преимущество. При "старой" методике между проверкой остатка и записью движения проходит время, в которое другой пользователь может успеть списать тот же товар. "Новая" методика решает проблему блокировок.

Алгоритм действий следующий:

Этот метод считается стандартом для высоконагруженных систем, так как он минимизирует время нахождения данных в состоянии блокировки.

Рекомендации по улучшению кода

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

1. Момент времени. При получении остатков всегда указывайте МоментВремени() документа. Это позволит правильно учитывать остатки, если вы перепроводите документ "задним числом". Использование просто ТекущаяДата() или Дата может привести к получению неактуальных данных.

2. Управляемые блокировки. Если ваша база работает в режиме управляемых блокировок, перед выполнением проверки (в "старой" методике) необходимо программно заблокировать данные регистра накопления по конкретным позициям СИЗ. Это гарантирует, что остатки не изменятся в процессе вашей проверки.

3. Группировка данных. Как уже было отмечено выше, всегда используйте метод Свернуть() для таблицы значений или СГРУППИРОВАТЬ ПО в запросе. Если пользователь введет одну и ту же номенклатуру в две разные строки документа, без группировки проверка может не сработать правильно (каждая строка по отдельности может проходить по остаткам, а их сумма — нет).

Подводя итог, можно сказать: для решения проблемы автора достаточно было заменить вложенный цикл на метод Найти() или объединить данные документа с остатками в одном запросе. Использование запросов — наиболее предпочтительный путь развития для любого 1С-программиста.

← На главную