При разработке в среде 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда записи в подчиненном регистре сведений ведут себя нестабильно: при первом проведении документа движения появляются, при втором — исчезают, а при третьем — снова возникают. Это классическая проблема, связанная с тем, как платформа управляет транзакциями и коллекциями движений документа. Рассмотрим подробно, почему так происходит и как правильно организовать запись данных в регистр.
Проанализируем типичный сценарий. Разработчик добавляет новый регистр сведений, подчиненный регистратору, и пытается заполнить его в расширении документа, используя обработчик После (или Вместо) процедуры ОбработкаПроведения. В коде обычно создается объект НаборЗаписей, устанавливается отбор по регистратору и вызывается метод Записать().
Основная причина нестабильности заключается в конфликте между ручной записью в базу данных и штатным механизмом коллекции Движения документа. Когда мы вызываем НаборЗаписей.Записать(), мы выполняем непосредственную запись в таблицу базы данных внутри транзакции проведения. Однако у объекта документа есть собственная коллекция ЭтотОбъект.Движения. Если наш регистр включен в состав движений документа, платформа в конце транзакции проведения анализирует эту коллекцию. Если в коллекции Движения для данного регистра не установлен флаг Записывать = Истина или коллекция пуста, платформа может «синхронизировать» состояние базы с объектом документа, фактически затирая наши ручные записи пустым набором.
Также важную роль играет свойство документа «Удаление движений». Если установлено «Удалять автоматически», платформа перед началом проведения очищает старые записи. Если мы записываем их вручную, а затем срабатывает какой-то штатный алгоритм очистки или перезаписи коллекции Движения, данные пропадают.
Самый правильный и надежный способ — не создавать отдельный объект НаборЗаписей вручную, а работать напрямую с коллекцией Движения самого документа. В этом случае платформа берет на себя управление транзакцией, очистку старых записей и установку отбора по регистратору.
Рассмотрим, как это реализовать в обработчике расширения. Выясним, что вместо создания набора через менеджер регистра, нам достаточно обратиться к свойству Движения:
&После("ОбработкаПроведения")
Процедура ИмяРегистра_ПослеОбработкаПроведения(Отказ, РежимПроведения)
// Платформа сама создаст набор записей и установит отбор по Ссылке документа
Для Каждого СтрокаТЧ Из ЭтотОбъект.Сотрудники Цикл
НоваяЗапись = ЭтотОбъект.Движения.ДниДляРасчетаДоплатыПоБольничному.Добавить();
НоваяЗапись.Период = ЭтотОбъект.Дата;
НоваяЗапись.Организация = ЭтотОбъект.Организация;
НоваяЗапись.Сотрудник = СтрокаТЧ.Сотрудник;
// Регистратор подставится автоматически платформой
КонецЦикла;
// Устанавливаем флаг, чтобы платформа записала эти движения в конце транзакции
ЭтотОбъект.Движения.ДниДляРасчетаДоплатыПоБольничному.Записывать = Истина;
КонецПроцедуры
Важный нюанс: Чтобы этот код работал, новый регистр сведений должен быть добавлен в состав движений документа в расширении. Если вы создали регистр в расширении, не забудьте зайти в свойства документа (в том же расширении) и на вкладке «Движения» отметить ваш регистр галочкой.
Если по каким-то причинам использование коллекции Движения невозможно (например, логика вынесена в подписку на событие, где объект документа доступен не всегда одинаково, или требуется сложная манипуляция наборами), необходимо соблюдать строгий порядок действий при работе с объектом СоздатьНаборЗаписей().
Посмотрим на пример кода, который решает проблему «через раз» при ручном подходе. Здесь критически важно использовать метод Прочитать() перед добавлением новых данных и явно указывать замещение при записи.
НаборЗаписей = РегистрыСведений.ДниДляРасчетаДоплатыПоБольничному.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ЭтотОбъект.Ссылка);
// Обязательно читаем существующие записи, чтобы платформа корректно отработала замещение
НаборЗаписей.Прочитать();
// Очищаем старые записи, если они были (или просто добавляем новые в чистый набор)
НаборЗаписей.Очистить();
Для Каждого Строка Из ТЧ_Данные Цикл
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Регистратор = ЭтотОбъект.Ссылка; // Явное указание регистратора
НоваяЗапись.Организация = ЭтотОбъект.Организация;
// Заполнение остальных измерений и ресурсов
КонецЦикла;
// Записываем с параметром Истина (замещение существующих записей по отбору)
НаборЗаписей.Записать(Истина);
Проанализируем ситуацию: метод Прочитать() гарантирует, что менеджер записи «увидел» текущее состояние регистра для этого документа. А Записать(Истина) принудительно стирает всё, что было в базе по данному регистратору, и записывает новый состав набора. Это исключает дублирование или пропуски при перепроведении.
Рассмотрим еще несколько важных моментов, которые помогут избежать ошибок при разработке:
ПриЗаписи или в расширении, убедитесь, что вы не записываете данные для документа, который еще не проведен или по которому получен Отказ = Истина.Движения.&Вместо, вы берете на себя полную ответственность за запись всех движений документа (и типовых, и ваших). Если вы используете &После, ваши записи делаются в самом конце, но до фиксации транзакции. В &После лучше всего просто наполнять коллекцию Движения и ставить флаг Записывать, позволяя платформе завершить процесс штатно.Подводя итог, можно сказать: в 99% случаев проблема «через раз» решается отказом от ручного создания НаборЗаписей внутри проведения в пользу использования ЭтотОбъект.Движения.ИмяРегистра. Это делает код чище, надежнее и полностью соответствует стандартам разработки фирмы 1С.