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