При разработке механизмов проведения документов в 1С программисты часто сталкиваются с ситуацией, когда при первом проведении документа всё работает корректно, а при повторном (перепроведении) — данные в регистрах «удваиваются», «схлопываются» или рассчитываются неверно. Особенно остро эта проблема стоит в задачах контроля остатков, когда документ должен списать ровно столько, сколько осталось по конкретному заказу или партии.
В этой статье мы подробно разберем ситуацию, описанную в теме, проанализируем причины возникновения ошибки и изучим несколько способов правильной перезаписи движений регистра накопления, чтобы ваши остатки всегда оставались актуальными.
Рассмотрим ситуацию: у нас есть документ ПриходОтПоставщика, который делает записи в регистр накопления ОжидаемыеПоступленияТМЦОтПоставщика. В коде обработчика проведения программист сначала получает текущие остатки по регистру, а затем на их основе формирует новые движения «Расход».
Основная ошибка заключается в том, что при перепроведении документа система может учитывать его собственные «старые» движения, которые уже записаны в базе данных. Если документ не очистил свои движения перед тем, как запросить остатки, запрос вернет данные с учетом текущего документа. В итоге получается замкнутый круг: документ видит свой предыдущий результат и на его основе делает новый расчет, который оказывается ошибочным.
Пример из темы: Заказ был на 150 единиц, пришло 100, остаток в регистре должен стать 50. Однако при повторном проведении код снова видит остаток, но уже искаженный предыдущей записью, и вместо корректировки создает некорректную запись.
Один из самых надежных способов, к которому в итоге пришел автор темы, — это явная очистка движений документа по конкретному регистру перед началом всех расчетов. Для этого используется объект НаборЗаписей.
Проанализируем программный код, который позволяет полностью «обнулить» движения текущего документа в конкретном регистре:
Набор = РегистрыНакопления.ОжидаемыеПоступленияТМЦОтПоставщика.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(ЭтотОбъект.Ссылка);
Набор.Записать();
Разберем по шагам, что здесь происходит:
НаборЗаписей для нужного нам регистра.Регистратор). Это гарантирует, что мы будем работать только с записями, которые сделал именно этот документ.Записать(). Поскольку набор записей пустой, а фильтр установлен на наш документ, 1С удаляет все существующие записи этого документа в регистре.После выполнения этого кода регистр становится «чистым» в контексте текущего документа, и последующий запрос к виртуальной таблице Остатки вернет верные данные, как если бы документ проводился впервые.
Если код пишется непосредственно в модуле объекта документа в процедуре ОбработкаПроведения, можно использовать более элегантный способ работы с коллекцией Движения. Рассмотрим этот вариант:
// Указываем, что движения по регистру нужно записывать
Движения.ОжидаемыеПоступленияТМЦОтПоставщика.Записывать = Истина;
// Очищаем текущую коллекцию движений в памяти
Движения.ОжидаемыеПоступленияТМЦОтПоставщика.Очистить();
// Сразу записываем пустую коллекцию в базу данных, чтобы освободить остатки для запроса
Движения.ОжидаемыеПоступленияТМЦОтПоставщика.Записать();
Этот метод предпочтительнее для платформы 8.3, так как он работает в рамках текущей транзакции проведения и использует встроенные механизмы объекта ДокументОбъект.
Профессиональный подход к решению задачи контроля остатков подразумевает, что нам даже не обязательно физически удалять старые движения перед запросом. Мы можем попросить систему: «Покажи мне остатки на момент времени сразу перед этим документом».
Для этого в 1С существует специальный объект Граница. Посмотрим, как изменить запрос для получения остатков:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ОжидаемыеОстатки.КолОстаток КАК КолОстаток
|ИЗ
| РегистрНакопления.ОжидаемыеПоступленияТМЦОтПоставщика.Остатки(&МоментВремени, ) КАК ОжидаемыеОстатки";
// Создаем границу, исключая текущий документ
Момент = Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая);
Запрос.УстановитьПараметр("МоментВремени", Момент);
Почему это важно: Использование ВидГраницы.Исключая гарантирует, что движения текущего документа (если они уже есть в базе) не попадут в расчет. Это избавляет от необходимости предварительной записи пустого набора и экономит время работы системы.
Проанализируем еще одну проблему из исходного сообщения — использование запроса внутри функции и последующий поиск строк в цикле. В исходном коде автор вызывал функцию ПолучитьОстаткиОжидаемыхПоступленийТМЦОтПоставщика, выгружал результат в массив и искал в нем данные через НайтиСтроки для каждой строки табличной части. Для оптимизации таких расчетов можно использовать базовый пример фильтрации данных временными таблицами в запросе.
Рассмотрим, как сделать это правильно и быстро:
Остатки регистра по ключевым полям (Номенклатура, Заказ, ВариантИсполнения).Это исключит «эффект замедления» при большом количестве строк в документе. Процессор 1С гораздо быстрее свяжет две таблицы в памяти SQL-сервера, чем будет выполнять поиск в массивах на клиенте или сервере приложений. При необходимости можно использовать наглядные примеры различных вариантов соединения таблиц в запросах.
Не стоит забывать и о настройках самого документа в Конфигураторе. У каждого документа есть свойство «Удаление движений» (Movement Deletion):
Если у вас возникла проблема, описанная в теме, проверьте, не стоит ли у документа свойство «Не удалять автоматически». Если стоит — значит, вы берете на себя полную ответственность за актуальность данных в регистрах при перепроведении.
Подводя итог, проанализируем основные правила работы с перезаписью регистров:
Граница с параметром ВидГраницы.Исключая, чтобы получить чистые данные на момент времени до документа.БлокировкаДанных.Применение этих подходов гарантирует стабильную работу системы, отсутствие «фантомных» остатков и корректное поведение программы при любых действиях пользователя с документами.