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