Как получить остаток по регистру после проведения документа в 1С?

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

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

Основные методы получения актуальных остатков

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

  1. Явная запись движений документа:

    Платформа 1С по умолчанию записывает все движения документа в базу данных только после полного завершения процедуры ОбработкаПроведения. Это означает, что если мы выполним запрос к регистру внутри этой процедуры до того, как движения будут фактически зафиксированы, мы получим остатки, которые не включают изменения, сделанные текущим документом. Чтобы избежать такой ситуации, мы должны явно записать движения с помощью метода Движения.Записать() или, если речь идет о конкретном регистре, Движения.<ИмяРегистра>.Записать().

    Разберем, как это выглядит в коде 1С, на примере процедуры ОбработкаПроведения в модуле объекта документа:

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

    Важно отметить, что поведение метода Движения.Записать() также зависит от свойства документа "Запись движений при проведении", которое настраивается в конфигураторе:

    • Если свойство установлено в "Записывать выбранные", то разработчик должен явно установить свойство Записывать в Истина для тех наборов движений, которые должны быть записаны.
    • Если свойство установлено в "Записывать модифицированные", то все модифицированные наборы записей будут автоматически записаны платформой в конце проведения.

    Несмотря на настройки свойства, явный вызов Движения.Записать() гарантирует фиксацию изменений в момент вызова. После явной записи свойство Записывать у записанных наборов движений сбрасывается в Ложь.

  2. Использование параметра Граница в запросах:

    Для корректного получения остатков, которые включают движения текущего документа, необходимо правильно указывать параметр периода в запросе к виртуальной таблице остатков регистра. Для этого мы используем объект Граница.

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

    
    // В запросе к виртуальной таблице остатков:
    // ...
    // ГДЕ РегистрОстатки.Период <= &МоментВремени
    // ...
    
    Запрос.УстановитьПараметр("МоментВремени", Новый Граница(МоментВремени(), ВидГраницы.Включая));
    

    При использовании ВидГраницы.Включая система учтет все движения до указанного момента времени включительно. Это обеспечивает, что движения текущего документа, которые мы предварительно записали методом Движения.Записать(), будут видны в запросе.

    Если же нам нужен момент сразу после позиции документа (например, для контроля остатков после всех подписок на события), можно использовать другие подходы, такие как Новый Граница(ДокументСсылка, ВидГраницы.Включая) или даже Новый Граница(МоментВремени().Дата + 1, ВидГраницы.НачалоДня). Однако для целей непосредственного контроля остатков в рамках текущего проведения документа Новый Граница(МоментВремени(), ВидГраницы.Включая) является наиболее подходящим и точным вариантом.

Контроль отрицательных остатков при неоперативном проведении

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

  1. Сложности неоперативного проведения:

    В стандартных бизнес-процессах и типовых конфигурациях 1С, при оперативном проведении, если документ пытается списать товар, которого нет на остатке, проведение просто отменяется. Однако при неоперативном проведении ситуация усложняется. Документ, проведенный задним числом, может сам по себе не создавать отрицательных остатков на свою дату, но при этом нарушить хронологию движений. Это, в свою очередь, может привести к появлению отрицательных остатков в более поздних, уже проведенных документах.

    Например, если документ прихода был удален или изменен задним числом, или документ расхода был вставлен между существующими приходами и расходами, это может вызвать "минуса" в уже проведенных документах, которые ранее были проведены корректно.

  2. Типовые сценарии манипуляций или ошибок:

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

    • Манипуляции с датами документов: Сотрудники могут намеренно проводить документы расхода задним числом, до фактического прихода товара, чтобы "закрыть" потребность. Либо, наоборот, изменять даты приходных документов, чтобы "закрыть" ранее образовавшиеся недостачи.
    • Изменение последовательности: Изменение или перепроведение документов-приходов или документов-расходов, следующих за текущим, может привести к тому, что ранее корректно проведенные документы начнут создавать отрицательные остатки, хотя их содержание не менялось.
    • Временные "затычки": Пользователь может временно ввести фиктивный документ прихода для проведения необходимого расхода, а затем удалить или сторнировать этот приход.
    • Отмена приходов: Простое удаление или отмена проведения документа-прихода может мгновенно привести к появлению минусов во всех последующих расходах.
  3. Подходы к решению проблемы контроля отрицательных остатков:

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

    • Автоматический контроль при проведении: Использование Движения.Записать() и запросов с МоментВремени(), как мы рассмотрели выше, позволяет контролировать остатки на дату текущего документа. Если обнаружен минус, документ не проводится. Это базовая, но необходимая мера.

    • Восстановление последовательности: Периодическое (или при изменении "задним числом" ключевых документов) восстановление последовательности проведения документов является классическим подходом для поддержания корректных остатков. Этот механизм перепроводит документы в хронологическом порядке, исправляя возможные нарушения.

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

    • "Блокировка" прошлых периодов: Для предотвращения изменений задним числом можно использовать механизмы установки даты запрета редактирования документов. Это административная мера, которая ограничивает возможности пользователей по изменению данных в уже закрытых периодах.

    • Дополнительные отчеты и аналитика: Разработка специализированных отчетов, которые показывают остатки по номенклатуре с детализацией по документам-движениям. Такие отчеты позволяют наглядно выявлять "дыры" в остатках и восстанавливать хронологию событий. Журнал регистрации также является ценным инструментом для отслеживания того, кто и когда проводил или перепроводил документы.

Расширенные возможности и подводные камни

Помимо основных методов, рассмотрим некоторые дополнительные аспекты и потенциальные ловушки, связанные с получением остатков:

  1. Событие ПриЗаписи модуля регистра:

    Некоторые разработчики могут рассматривать возможность контроля остатков в событии ПриЗаписи модуля набора записей регистра. Однако это событие вызывается при явной записи набора регистра (например, после Движения.Записать()). Использование его для контроля остатков всего документа может быть менее удобным, чем проверка в ОбработкаПроведения, так как оно работает на уровне отдельного набора записей, а не всего документа в целом. Кроме того, в этом событии мы не имеем прямого доступа к отказу проведения всего документа.

  2. Подписки на события:

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

  3. Синхронизация периода движений и даты документа:

    При программном вызове метода Записать() у объекта документа, событие ПередЗаписью формы документа не вызывается. Поэтому для синхронизации периода движений документа с датой документа или для выполнения специфических проверок, связанных с датой, следует использовать обработчик события ПередЗаписью *объекта* документа, а не формы. Это гарантирует, что логика будет выполняться независимо от того, как документ записывается (через форму или программно), обеспечивая консистентность данных.

Таким образом, для надежного получения актуальных остатков по регистру непосредственно после проведения документа, мы явно записываем движения документа с помощью Движения.Записать() (или Движения.<ИмяРегистра>.Записать()) и затем выполняем запрос к виртуальной таблице остатков регистра, используя Новый Граница(МоментВремени(), ВидГраницы.Включая). При работе с неоперативным проведением и контролем отрицательных остатков, важно применять комплексный подход, включающий не только проверки в момент проведения, но и механизмы восстановления последовательности, регламентные задания и отчетность для выявления и предотвращения проблем, связанных с нарушением хронологии движений.

← На главную