Почему пропадают движения при программной записи документа "Корректировка регистров" и как это исправить?

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

При программном создании документов в среде «1С:Предприятие 8» разработчики часто сталкиваются со странным поведением системы: документ создается, движения по регистрам вроде бы формируются, но после выполнения кода или повторного открытия документа таблицы движений оказываются абсолютно пустыми. Особенно часто этот эффект проявляется при работе со служебным документом КорректировкаРегистров в типовых конфигурациях, таких как «Управление торговлей 11», «ERP Управление предприятием» или «Комплексная автоматизация».

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

В чем кроется причина проблемы? Разбираем «под капотом»

Для начала давайте проанализируем типичный ошибочный код, который часто пишут начинающие разработчики (именно такой пример представлен в исходном сообщении на форуме):


// ОШИБОЧНЫЙ ПРИМЕР! НЕ ИСПОЛЬЗУЙТЕ ЭТОТ КОД
ДокКорректировка = Документы.КорректировкаРегистров.СоздатьДокумент();
ДокКорректировка.Дата = ТекущаяДата();
ДокКорректировка.Записать(); // Первая запись документа

нбВыручкаИСебестоимостьПродаж = ДокКорректировка.Движения.ВыручкаИСебестоимостьПродаж;
нбВыручкаИСебестоимостьПродаж.Записывать = Истина;

НовДвижение = нбВыручкаИСебестоимостьПродаж.Добавить();
НовДвижение.Активность = Истина;
нбВыручкаИСебестоимостьПродаж.Записать(); // Запись набора записей регистра напрямую в БД

ДокКорректировка.Записать(); // Вторая запись документа (после которой всё пропадает!)

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

1. Конфликт кэша оперативной памяти и базы данных (эффект двойной записи).

Когда мы создаем объект ДокКорректировка в оперативной памяти, его коллекция Движения инициализируется как пустая. После первой записи документа в базу данных мы получаем ссылку. Далее мы обращаемся к набору записей регистра через свойство Движения, добавляем строку и записываем этот набор напрямую в базу данных методом нбВыручкаИСебестоимостьПродаж.Записать(). На этом этапе данные физически попали в СУБД.

Но обратите внимание: сам объект ДокКорректировка в оперативной памяти ничего не знает о том, что данные в СУБД обновились. В его внутреннем кэше коллекция движений по регистру «Выручка и себестоимость продаж» по-прежнему числится пустой. Когда в конце кода вызывается метод ДокКорректировка.Записать(), платформа инициирует стандартную запись документа и всех его связанных движений. Она видит, что в памяти коллекция пуста, и послушно транслирует это пустое состояние в базу данных, перезаписывая (и тем самым полностью удаляя!) те данные, которые мы только что успешно записали напрямую.

2. Механизм автоматической очистки в модуле документа.

В типовых конфигурациях (ERP, КА, УТ) документ КорректировкаРегистров имеет табличную часть под названием ТаблицаРегистров. В ней хранятся имена регистров, по которым данный документ должен делать движения. В обработчике события ПередЗаписью модуля этого документа прописан жесткий алгоритм: система обходит все регистры конфигурации, и если по какому-то регистру в базе данных существуют записи с регистратором данного документа, но имя этого регистра отсутствует в табличной части ТаблицаРегистров, то система принудительно очищает движения по этому регистру. Это сделано для интерактивной работы пользователя на форме (чтобы при снятии галочки с регистра на форме его старые движения автоматически удалялись).

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

3. Отсутствие обязательного реквизита "Период".

В коде-примере у движения заполняется только свойство Активность = Истина. Но для любого регистра накопления или сведений поле Период является строго обязательным на уровне платформы. Если оно не заполнено явно, платформа попытается записать туда пустую дату (01.01.0001). Во многих СУБД (например, Microsoft SQL Server) это вызывает ошибку диапазона дат или приводит к неявному отказу транзакции при проведении/записи, из-за чего данные попросту не сохраняются.

---

Правильный подход №1. Запись через объект документа (Рекомендуемый)

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

Давайте разберем этот процесс по шагам на конкретном примере:


// Шаг 1. Инициализируем создание документа корректировки
ДокКорректировка = Документы.КорректировкаРегистров.СоздатьДокумент();
ДокКорректировка.Дата = Дата; // Указываем актуальную дату документа
ДокКорректировка.Ответственный = Пользователи.ТекущийПользователь();

// Заполняем обязательный реквизит для типовых конфигураций
ДокКорректировка.Операция = Перечисления.ОперацииКорректировкиРегистров.РучнаяКорректировка;

// Шаг 2. Обязательно регистрируем наш регистр в табличной части документа
СтрокаТаблицаРегистров = ДокКорректировка.ТаблицаРегистров.Добавить();
СтрокаТаблицаРегистров.Имя = "ВыручкаИСебестоимостьПродаж";

// Шаг 3. Обращаемся к коллекции движений прямо из объекта документа
ДвиженияВыручки = ДокКорректировка.Движения.ВыручкаИСебестоимостьПродаж;

// Взводим флаг необходимости записи этой коллекции в БД при записи документа
ДвиженияВыручки.Записывать = Истина;

// Шаг 4. Добавляем и заполняем строки движений
НоваяСтрока = ДвиженияВыручки.Добавить();
НоваяСтрока.Период = ДокКорректировка.Дата; // Обязательно указываем период!
НоваяСтрока.Активность = Истина;

// Заполняем необходимые измерения регистра
// НоваяСтрока.АналитикаУчетаНоменклатуры = ...
// НоваяСтрока.Договор = ...

// Заполняем ресурсы регистра
// НоваяСтрока.Выручка = 15000;
// НоваяСтрока.Себестоимость = 10000;

// Обратите внимание: реквизит "Регистратор" заполнять вручную НЕ НУЖНО.
// Платформа 1С автоматически подставит ссылку на документ при его записи.

// Шаг 5. Записываем документ ОДИН раз
ДокКорректировка.Записать(РежимЗаписиДокумента.Запись);

Преимущества этого способа:

  1. Высокая производительность: происходит всего одно обращение к базе данных для записи документа и движений.
  2. Консистентность данных: запись происходит в одной транзакции СУБД.
  3. Простота кода: вам не нужно контролировать ссылки и вручную заполнять измерение Регистратор.

---

Правильный подход №2. Запись через независимый набор записей (с обходом стандартной логики)

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

Рассмотрим по шагам, как правильно реализовать этот подход:


// Шаг 1. Создаем и записываем документ-контейнер для получения ссылки
ДокКорректировка = Документы.КорректировкаРегистров.СоздатьДокумент();
ДокКорректировка.Дата = Дата;
ДокКорректировка.Ответственный = Пользователи.ТекущийПользователь();

// Обязательно регистрируем регистр в ТЧ, чтобы стандартные механизмы не удалили движения
СтрокаТаблицаРегистров = ДокКорректировка.ТаблицаРегистров.Добавить();
СтрокаТаблицаРегистров.Имя = "ВыручкаИСебестоимостьПродаж";

// Записываем документ, чтобы получить ссылку в БД
ДокКорректировка.Записать(РежимЗаписиДокумента.Запись);

// Ссылка теперь гарантированно создана
СсылкаНаДокумент = ДокКорректировка.Ссылка;

// Шаг 2. Создаем набор записей регистра напрямую через менеджер регистра
НаборЗаписей = РегистрыНакопления.ВыручкаИСебестоимостьПродаж.СоздатьНаборЗаписей();

// Обязательно устанавливаем отбор по нашему регистратору
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);

// Шаг 3. Заполняем набор движений
НовоеДвижение = НаборЗаписей.Добавить();
НовоеДвижение.Период = ДокКорректировка.Date; // Период должен быть заполнен
НовоеДвижение.Активность = Истина;
НовоеДвижение.Регистратор = СсылкаНаДокумент; // Для независимого набора явно указываем регистратор

// Заполняем измерения и ресурсы...
// НовоеДвижение.Выручка = 15000;

// Шаг 4. Включаем режим загрузки для обхода стандартных обработчиков (если применимо)
НаборЗаписей.ОбменДанными.Загрузка = Истина;

// Шаг 5. Записываем набор в базу данных
НаборЗаписей.Записать();

// ВНИМАНИЕ! После этого вызывать ДокКорректировка.Записать() больше НЕЛЬЗЯ.
// Любая последующая запись объекта документа из памяти сотрет результаты вашей работы с набором.

---

Критически важное ограничение для ERP и КА: "Онлайн-взаиморасчеты"

Если вы планируете выполнять программную корректировку регистров, связанных с расчетами с контрагентами (например, таких регистров как РасчетыСКлиентамиПоСрокам или РасчетыСПоставщикамиПоСрокам), важно учитывать одну важную деталь современных версий ERP 2.x, КА 2.x и УТ 11.x.

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

Для исправления таких взаиморасчетов необходимо программно корректировать основные регистры (например, РасчетыСКлиентами), а затем программно запускать обработку актуализации расчетов, чтобы система самостоятельно сформировала корректные движения по подчиненным регистрам "по срокам".

Резюме: правила безопасной работы с корректировками

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

  1. Записывайте объект только один раз. Избегайте конструкций с промежуточными вызовами методов Записать() у разных связанных объектов.
  2. Синхронизируйте ТЧ "ТаблицаРегистров". Любой регистр, по которому делаются движения, должен быть явно прописан строкой в табличной части документа.
  3. Заполняйте "Период". Это системное поле должно быть всегда заполнено актуальной датой (обычно равной дате самого документа).
  4. Определяйте вид движения. Для регистров накопления всегда явно указывайте вид движения: приход или расход (с помощью системного перечисления ВидДвиженияНакопления).
← На главную