Перезапись движений и записей регистров в 1С 8.3

· 10 мин

Содержание

Запись регистра перезаписывают двумя способами. Если запись принадлежит документу-регистратору — её меняют через коллекцию Движения в обработчике проведения: старый набор замещается новым. Если регистр сведений независимый — пишут напрямую через менеджер записи или набор записей с методом Записать(Истина), и прежнее значение по тем же ключам затирается.

Разница между этими путями — не стилистическая. Подчинённый регистратору набор нельзя дописать «в обход» документа, а независимый регистр не умеет сам чистить лишние строки — за это отвечает параметр замещения. Ниже — механика движений, оба режима записи, работа с табличными частями, блокировки и то, как не положить базу при массовой перезаписи.

Как документ формирует движения по регистрам

Движения документа рождаются в обработчике ОбработкаПроведения(Отказ, РежимПроведения) в модуле документа. Платформа вызывает его при проведении, передаёт флаг Отказ (можно прервать проведение) и РежимПроведения — оперативный или неоперативный.

Внутри обработчика заполняют коллекции Движения.<ИмяРегистра>. Это наборы записей, жёстко привязанные к текущему документу как к регистратору. Типичный фрагмент:

Движение = Движения.ТоварыНаСкладах.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
Движение.Склад = Склад;
Движение.Количество = СтрокаТЧ.Количество;

Чтобы движения попали в базу, их нужно записать. Два варианта:

  • 📌 Движения.Записывать = Истина; — платформа запишет все заполненные наборы в конце проведения автоматически. Так пишут в типовых конфигурациях.
  • 📌 Движения.ТоварыНаСкладах.Записать(); — явная запись одного набора прямо сейчас, если движения нужны до конца обработчика (например, чтобы прочитать остатки).

Важна настройка документа в конфигураторе — свойство «Удаление движений». При значении «Удалять автоматически» платформа сама стирает прежние движения регистратора перед вызовом ОбработкаПроведения, поэтому старый набор не накладывается на новый. Если выбрано «Не удалять автоматически», чистить придётся вручную через Движения.<Регистр>.Очистить() — иначе при перепроведении строки задвоятся.

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

Режимы записи движений: замещение и добавление

Любой набор записей регистра пишется в одном из двух режимов — они задаются булевым параметром метода Записать(Замещение).

РежимПараметрЧто делает
ЗамещениеЗаписать(Истина)Удаляет все записи по текущему отбору (у движений — по регистратору) и пишет новый набор
ДобавлениеЗаписать(Ложь)Дописывает записи, не трогая существующие

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

Если перепровести документ и движения не изменились так, как вы ждали, проверьте две вещи: заполняется ли Движения.Записывать и не стоит ли у документа «Не удалять автоматически» при пустой ручной очистке.

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

Прямая запись в независимый регистр сведений

Независимый регистр сведений (не подчинён регистратору) перезаписывают напрямую, без документа. Здесь два инструмента — выбирайте по задаче.

Менеджер записи — когда перезаписываете одну конкретную запись по полному ключу (все измерения плюс период для периодического регистра):

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

Параметр Истина здесь означает: если запись с таким ключом уже есть — перезаписать её. При Записать(Ложь) и существующей записи платформа вернёт ошибку «Запись с такими ключевыми полями уже существует». Прочитать текущее значение перед правкой можно через Запись.Прочитать(), а проверить, нашлось ли оно, — через Запись.Выбран().

Шаблон «прочитать — изменить — записать» для одного ресурса выглядит так:

Запись = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
Запись.Период = ТекущаяДата();
Запись.Номенклатура = ТоварСсылка;
Запись.ТипЦен = ТипЦенСсылка;
Запись.Прочитать();
Если Запись.Выбран() Тогда
    Запись.Цена = Запись.Цена * 1.1; // подняли на 10%
Иначе
    Запись.Цена = 0;
КонецЕсли;
Запись.Записать(Истина);

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

Набор записей с отбором — когда перезаписываете пачку строк по одному-двум измерениям:

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

Записать(Истина) сначала удалит все записи, подходящие под Отбор (здесь — все цены по этой номенклатуре), а затем запишет то, что вы положили в набор. Если набор оставить пустым и записать с замещением — записи по отбору просто удалятся. Это штатный приём очистки.

Отбор у набора должен совпадать по составу с тем, что вы перезаписываете. Поставили отбор только по номенклатуре, а в наборе строки с разными ТипЦен — замещение снесёт цены по всем типам этой номенклатуры, не только по нужному.

Перезапись движений, связанных с табличной частью

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

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

  1. Меняете строки табличной части документа.
  2. Проводите документ заново (из формы — кнопка проведения, программно — Документ.Записать(РежимЗаписиДокумента.Проведение)).
  3. Обработчик проведения перечитывает табличную часть и формирует свежий набор.

Если движений по регистру быть не должно вовсе (например, строка удалена и регистр стал пустым), набор всё равно записывается — пустой. Пустой набор с замещением удаляет прежние движения, и регистр по этому документу очищается.

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

Движения.ТоварыНаСкладах.БлокироватьДляИзменения = Истина;

Это поднимает блокировку по измерениям, которые пишутся, и защищает от гонки, когда два документа одновременно списывают один и тот же товар и оба «не видят» списание друг друга.

Блокировки и конфликты при одновременной записи

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

Две типовые проблемы:

  • 🔴 Взаимоблокировка — ошибка «Обнаружена взаимоблокировка транзакций». Два сеанса захватывают одни и те же ресурсы в разном порядке и ждут друг друга. Лечится единым порядком блокировок: всегда блокируйте регистры в одной и той же последовательности, а блокировку ставьте до чтения остатков, а не после.
  • 🔴 Таймаут — «Превышено время ожидания предоставления блокировки». Длинная транзакция держит ресурс, остальные ждут. Лечится сокращением транзакций: не выполняйте долгие расчёты и обращения к внешним сервисам внутри проведения.

Явная управляемая блокировка перед чтением выглядит так:

Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах.Остатки");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
Элемент.УстановитьЗначение("Номенклатура", ТоварСсылка);
Блокировка.Заблокировать();

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

Как ускорить перезапись больших наборов

Массовая перезапись (перепроведение тысяч документов, пересчёт цен по всей номенклатуре) упирается не в логику, а в число обращений к базе. Приёмы, которые дают основной выигрыш:

Пишите набором, а не в цикле по строкам. Самая частая ошибка — создавать менеджер записи на каждую строку внутри цикла. Это сотни отдельных транзакций. Соберите набор целиком и запишите один раз:

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

Индексируйте измерения, по которым идёт отбор при записи и чтении. Без индекса каждый отбор — полный перебор таблицы регистра.

При загрузке данных отключайте бизнес-логику. Если вы переносите готовые движения и не хотите запускать контроль проведения, установите у набора ОбменДанными.Загрузка = Истина перед записью — платформа пропустит часть проверок и обработчиков. Применяйте осознанно: данные должны быть уже корректными.

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

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

Учитывайте пересчёт итогов. У регистров накопления и бухгалтерии платформа хранит рассчитанные итоги для быстрого получения остатков. Каждая перезапись движений обновляет итоги — это заметная доля времени массовой операции. Перед крупной загрузкой итоги по регистру отключают, а после — включают и пересчитывают разом; это быстрее, чем пересчитывать их на каждую запись.

Перед массовой перезаписью регистров
Выполнено: 0 / 6

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

Как перезаписать запись регистра — какой у вас случай?

Когда перезапись заменяют сторнированием

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

Механика сторно для регистра остатков — это движение, зеркальное ошибочному:

  • ⏪ было списание Расход 10 шт — сторно добавляет Приход 10 шт в дату исправления;
  • ⏩ затем, если нужно, формируется правильное движение уже корректной датой.

Итог по регистру сходится, но история остаётся прозрачной: видно и ошибку, и исправление. Для регистров бухгалтерии ту же роль играет операция сторнирования с отрицательной суммой. Решение между «перезаписать» и «сторнировать» принимают не из удобства кода, а из того, закрыт период или нет.

Правило простое: открытый период — перепроводите и перезаписывайте движения; закрытый — исправляйте сторнирующей записью в открытой дате, прошлое не трогайте.

Частые вопросы

Чем менеджер записи отличается от набора записей?
Менеджер работает с одной записью по полному ключу (все измерения + период) — удобно для точечной перезаписи. Набор работает с группой записей по отбору и замещает их пачкой. Для независимого регистра доступны оба, для подчинённого регистратору — только набор через движения документа.
Почему `Записать(Ложь)` для регистра сведений выдаёт ошибку о дублировании ключа?
Режим добавления не проверяет и не затирает существующую запись. Если запись с таким набором ключевых полей уже есть, добавить вторую нельзя — ключ уникален. Для перезаписи нужен режим замещения: Записать(Истина).
Как перезаписать движения, не проводя документ заново?
Для набора, подчинённого регистратору, это не рекомендуется: движения должны соответствовать данным документа. Технически можно получить набор через РегистрыНакопления.<Имя>.СоздатьНаборЗаписей(), поставить отбор по регистратору и переписать, но при следующем перепроведении документ всё равно пересчитает движения по-своему.
Что делает пустой набор с замещением?
Удаляет все записи, подходящие под установленный отбор, и ничего не пишет взамен. Это штатный способ очистить движения документа или строки независимого регистра.
Почему при перепроведении движения задваиваются?
Скорее всего, у документа отключено автоматическое удаление движений, а ручная очистка (Движения.<Регистр>.Очистить()) не выполняется. Либо набор пишется в режиме добавления вместо замещения.