При разработке механизмов контроля остатков в системе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда, несмотря на наличие программного кода проверки, в регистрах все равно появляются отрицательные значения. Особенно часто это происходит при использовании объекта Граница с видом ВидГраницы.Исключая. В данной статье мы подробно разберем, как работают механизмы позиционирования документов на временной оси, почему «старый» метод контроля остатков дает сбои и как правильно организовать проверку, чтобы избежать логических ошибок.
Для начала проанализируем, как платформа 1С идентифицирует положение документа на временной оси. Существует понятие Момента времени, которое включает в себя две составляющие: Дату (с точностью до секунды) и Ссылку на объект (уникальный идентификатор GUID). Рассмотрим, зачем это нужно. Если в одну и ту же секунду создано несколько документов, системе необходимо понимать, какой из них был «раньше», а какой «позже».
Объект Граница является надстройкой над моментом времени или датой. Он указывает системе, нужно ли включать движения конкретной точки в выборку или нет. Существует два вида границ:
Рассмотрим ситуацию, описанную автором темы. Пользователь создает «Приход» на 10 единиц товара, затем «Расход» на 10 единиц. Позже создается еще один «Расход» копированием. При перепроведении документов из прошлого система позволяет списать товар в минус. Почему это происходит?
Проанализируем работу запроса с использованием ВидГраницы.Исключая. Когда мы указываем этот вид границы для текущего документа, система получает остатки строго до него. Если документ проводится «задним числом» (неоперативно), то запрос на его момент времени видит только те остатки, которые были в системе на тот исторический момент. Он совершенно «не знает» о том, что через час или на следующий день другой документ уже списал этот товар.
Пример: Документ №1 (10:00:00) — Приход 10 шт. Остаток стал 10. Документ №2 (12:00:00) — Расход 10 шт. Остаток стал 0. Мы решили провести Документ №1.5 (11:00:00). Система проверяет остатки на 11:00:00. В этот момент на складе 10 шт. Проверка проходит успешно, Документ №1.5 списывает еще 10 шт. Итог: на 11:00:01 остаток 0, но на 12:00:01 (после Документа №2) остаток становится -10.
Важно понимать, что если два документа имеют одинаковое время (вплоть до секунды), порядок их следования определяется по ссылке. Платформа сравнивает GUID документов. В рамках одной информационной базы документы, созданные позже, обычно имеют «большую» ссылку, но это не является гарантированным правилом (особенно в распределенных базах или при программной генерации ссылок). При использовании Границы система четко отсекает все, что находится «после» текущего момента времени, включая документы с «большим» GUID в этой же секунде.
В современной практике 1С (так называемый «новый метод» контроля) считается неправильным запрашивать остатки до записи движений документа. Рассмотрим, почему эффективнее сначала записывать движения, а потом проверять результат.
Алгоритм должен выглядеть следующим образом:
Движения.Остатки.Записывать = Истина).Границу с видом Включая.КоличествоОстаток < 0.Отказ = Истина).Посмотрим на пример кода, реализующий получение остатков после записи движений текущего документа:
// В обработке проведения
Движения.Остатки.Записывать = Истина;
Движения.Записать(); // Фиксируем движения в базе
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОстаткиТоваровОстатки.Номенклатура,
| ОстаткиТоваровОстатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки(
| &ГраницаКонтроля,
| Номенклатура В (&СписокНоменклатуры)
| ) КАК ОстаткиТоваровОстатки
|ГДЕ
| ОстаткиТоваровОстатки.КоличествоОстаток < 0";
// Используем Включая, чтобы увидеть результат своих же движений
Момент = Новый МоментВремени(Дата, Ссылка);
ГраницаКонтроля = Новый Граница(Момент, ВидГраницы.Включая);
Запрос.УстановитьПараметр("ГраницаКонтроля", ГраницаКонтроля);
Запрос.УстановитьПараметр("СписокНоменклатуры", СписокТоваров);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Отказ = Истина;
// Сообщить пользователю о нехватке
КонецЕсли;
Проанализируем аргументацию автора темы о том, что «записывать, а потом проверять — слишком дорого». На самом деле, это заблуждение. Современные СУБД (MS SQL Server, PostgreSQL) оптимизированы для работы внутри транзакции. Запись движений и последующий откат (Rollback) в случае неудачи — это штатная и быстрая операция. Попытка же прочитать остатки «до» записи часто приводит к необходимости делать это дважды или использовать сложные блокировки, что нагружает систему гораздо сильнее.
Более того, только «новый метод» гарантирует отсутствие «минусов» при неоперативном проведении, если запрос будет проверять остатки не только на момент документа, но и на текущий момент (или по всем последующим точкам, где остаток мог стать отрицательным).
Проанализировав ситуацию, мы пришли к следующим выводам:
Граница(..., ВидГраницы.Исключая) отсекает текущий документ и всё, что создано после него.ВидГраницы.Включая.