Как правильно реализовать перезапись движений регистра при перепроведении документа в 1С

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

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

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

Суть проблемы: почему остатки рассчитываются неверно

Рассмотрим ситуацию: у нас есть документ ПриходОтПоставщика, который делает записи в регистр накопления ОжидаемыеПоступленияТМЦОтПоставщика. В коде обработчика проведения программист сначала получает текущие остатки по регистру, а затем на их основе формирует новые движения «Расход».

Основная ошибка заключается в том, что при перепроведении документа система может учитывать его собственные «старые» движения, которые уже записаны в базе данных. Если документ не очистил свои движения перед тем, как запросить остатки, запрос вернет данные с учетом текущего документа. В итоге получается замкнутый круг: документ видит свой предыдущий результат и на его основе делает новый расчет, который оказывается ошибочным.

Пример из темы: Заказ был на 150 единиц, пришло 100, остаток в регистре должен стать 50. Однако при повторном проведении код снова видит остаток, но уже искаженный предыдущей записью, и вместо корректировки создает некорректную запись.

Способ 1. Принудительная очистка движений через Набор Записей

Один из самых надежных способов, к которому в итоге пришел автор темы, — это явная очистка движений документа по конкретному регистру перед началом всех расчетов. Для этого используется объект НаборЗаписей.

Проанализируем программный код, который позволяет полностью «обнулить» движения текущего документа в конкретном регистре:


Набор = РегистрыНакопления.ОжидаемыеПоступленияТМЦОтПоставщика.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(ЭтотОбъект.Ссылка);
Набор.Записать();

Разберем по шагам, что здесь происходит:

  1. Мы создаем пустой объект НаборЗаписей для нужного нам регистра.
  2. Устанавливаем фильтр (отбор) по текущему документу (Регистратор). Это гарантирует, что мы будем работать только с записями, которые сделал именно этот документ.
  3. Вызываем метод Записать(). Поскольку набор записей пустой, а фильтр установлен на наш документ, 1С удаляет все существующие записи этого документа в регистре.

После выполнения этого кода регистр становится «чистым» в контексте текущего документа, и последующий запрос к виртуальной таблице Остатки вернет верные данные, как если бы документ проводился впервые.

Способ 2. Использование свойства «Записывать» и метода «Очистить»

Если код пишется непосредственно в модуле объекта документа в процедуре ОбработкаПроведения, можно использовать более элегантный способ работы с коллекцией Движения. Рассмотрим этот вариант:


// Указываем, что движения по регистру нужно записывать
Движения.ОжидаемыеПоступленияТМЦОтПоставщика.Записывать = Истина;

// Очищаем текущую коллекцию движений в памяти
Движения.ОжидаемыеПоступленияТМЦОтПоставщика.Очистить();

// Сразу записываем пустую коллекцию в базу данных, чтобы освободить остатки для запроса
Движения.ОжидаемыеПоступленияТМЦОтПоставщика.Записать();

Этот метод предпочтительнее для платформы 8.3, так как он работает в рамках текущей транзакции проведения и использует встроенные механизмы объекта ДокументОбъект.

Способ 3. Получение остатков на момент «ДО» документа (Граница)

Профессиональный подход к решению задачи контроля остатков подразумевает, что нам даже не обязательно физически удалять старые движения перед запросом. Мы можем попросить систему: «Покажи мне остатки на момент времени сразу перед этим документом».

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ОжидаемыеОстатки.КолОстаток КАК КолОстаток
    |ИЗ
    |   РегистрНакопления.ОжидаемыеПоступленияТМЦОтПоставщика.Остатки(&МоментВремени, ) КАК ОжидаемыеОстатки";

// Создаем границу, исключая текущий документ
Момент = Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая);
Запрос.УстановитьПараметр("МоментВремени", Момент);

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

Оптимизация: Запрос в цикле и соединение таблиц

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

Рассмотрим, как сделать это правильно и быстро:

  1. Выгрузим табличную часть документа во временную таблицу прямо в запросе.
  2. Соединим эту временную таблицу с виртуальной таблицей Остатки регистра по ключевым полям (Номенклатура, Заказ, ВариантИсполнения).
  3. Получим все необходимые данные (сколько пришло по документу и сколько осталось в регистре) одним запросом.

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

Настройка автоматического удаления движений

Не стоит забывать и о настройках самого документа в Конфигураторе. У каждого документа есть свойство «Удаление движений» (Movement Deletion):

Если у вас возникла проблема, описанная в теме, проверьте, не стоит ли у документа свойство «Не удалять автоматически». Если стоит — значит, вы берете на себя полную ответственность за актуальность данных в регистрах при перепроведении.

Заключение и рекомендации

Подводя итог, проанализируем основные правила работы с перезаписью регистров:

  1. Всегда очищайте движения перед запросом остатков, если используете «старую» методику проведения (сначала расчет, потом запись).
  2. Используйте объект Граница с параметром ВидГраницы.Исключая, чтобы получить чистые данные на момент времени до документа.
  3. Избегайте запросов в циклах. Получайте все необходимые данные одним пакетом, соединяя данные документа с данными регистров.
  4. Не забывайте про блокировку данных. Если два документа одновременно будут рассчитывать остатки по одному заказу, может возникнуть ситуация «коллизии», когда оба документа увидят один и тот же остаток и спишут его дважды. Используйте объект БлокировкаДанных.

Применение этих подходов гарантирует стабильную работу системы, отсутствие «фантомных» остатков и корректное поведение программы при любых действиях пользователя с документами.

← На главную