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