В процессе разработки на платформе 1С часто возникает специфическая задача: необходимо сделать так, чтобы движения по определенному регистру накопления "замерли". Представим ситуацию, когда у нас есть десять различных видов документов, которые являются регистраторами для одного регистра. Бизнес-логика требует, чтобы при повторном проведении или даже при полной отмене проведения документа записи в этом конкретном регистре оставались неизменными, как при первом проведении. При этом крайне нежелательно вносить изменения в модули проведения каждого из десяти документов.
В этой статье мы подробно разберем, как реализовать механизм "неприкосновенности" движений, используя возможности платформы 1С, такие как подписки на события и манипуляции с наборами записей.
Прежде чем переходить к решению, проанализируем стандартное поведение платформы. По умолчанию для любого документа установлено свойство Удаление движений в значение Удалять автоматически при отмене проведения. Кроме того, при перепроведении (которое по сути является последовательностью "отмена проведения" + "проведение") система сначала очищает старые записи регистратора, чтобы записать новые актуальные данные. Это гарантирует актуальность итогов регистра, но в нашей задаче это поведение является препятствием.
Чтобы не "лезть" в модуль каждого документа, мы можем использовать механизм Подписки на события. Это позволит нам централизованно управлять процессом записи движений для всех нужных типов документов в одном месте.
Рассмотрим пошагово, как это реализовать:
Источник выберем все типы документов, которые не должны изменять движения нашего регистра.ОбработкаПроведения (для предотвращения изменения при перепроведении) и ОбработкаУдаленияПроведения (для сохранения записей при распроведении).Движения.Проанализируем пример кода для такой подписки:
Процедура СохранитьДвиженияПриПроведении(Источник, Отказ, РежимПроведения) Экспорт
// Обращаемся к набору записей нашего регистра через коллекцию движений документа
НаборДвижений = Источник.Движения.ИмяВашегоРегистра;
// Если нам нужно, чтобы старые записи не трогались,
// мы просто запрещаем платформе записывать этот набор автоматически
НаборДвижений.Записывать = Ложь;
КонецПроцедуры
Установив Записывать = Ложь, мы сообщаем платформе, что в рамках данной транзакции этот конкретный регистр трогать не нужно. Существующие записи в базе данных останутся на месте, а новые не будут созданы.
Если по каким-то причинам использование подписок неудобно, можно попытаться воздействовать на процесс непосредственно "изнутри" самого регистра. Для этого используется модуль набора записей регистра накопления. Нам понадобится обработчик события ПередЗаписью.
Рассмотрим ситуацию: когда документ перепроводится, платформа пытается записать в регистр сначала пустой набор (очистка), а затем новый набор. Мы можем перехватить этот момент и, если это не первая запись документа, подменить записываемый набор данными, которые уже лежат в базе.
Посмотрим на пример реализации такого метода:
Процедура ПередЗаписью(Отказ, Замещение)
// Если включен режим обмена данными, ничего не делаем
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Проверяем, не является ли набор записей уже заполненным (например, при первой записи)
// В данном примере мы хотим всегда восстанавливать старые данные
РегистраторСсылка = Отбор.Регистратор.Значение;
Если ЗначениеЗаполнено(РегистраторСсылка) Тогда
// Создаем запрос, чтобы прочитать текущие данные регистра из БД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ *
|ИЗ РегистрНакопления.ИмяВашегоРегистра
|ГДЕ Регистратор = &Регистратор";
Запрос.УстановитьПараметр("Регистратор", РегистраторСсылка);
ТаблицаСтарыхЗаписей = Запрос.Выполнить().Выгрузить();
Если ТаблицаСтарыхЗаписей.Количество() > 0 Тогда
// Если в базе уже есть записи по этому регистратору,
// мы очищаем текущий (возможно пустой) набор и загружаем старые данные
ЭтотОбъект.Очистить();
Для Каждого СтрокаТЗ Из ТаблицаСтарыхЗаписей Цикл
ЗаполнитьЗначенияСвойств(ЭтотОбъект.Добавить(), СтрокаТЗ);
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Важное замечание: Этот метод довольно "тяжелый" с точки зрения производительности, так как при каждой попытке записи регистра система будет выполнять дополнительный запрос к базе данных. Также стоит быть осторожным с бесконечной рекурсией, хотя при записи набора записей ПередЗаписью обычно не вызывает повторного входа, если не вызывается метод Записать().
Чтобы сделать механизм более гибким, мы можем комбинировать подписку на события и модуль набора записей через ДополнительныеСвойства. Это позволит нам отличать программную перезапись от штатного проведения документа.
В подписке на событие ПередЗаписью документа мы можем пометить набор записей флагом:
Источник.Движения.ИмяВашегоРегистра.ДополнительныеСвойства.Вставить("НеМенятьДвижения", Истина);
А в модуле набора записей регистра проверим этот флаг:
Процедура ПередЗаписью(Отказ, Замещение)
Если ДополнительныеСвойства.Свойство("НеМенятьДвижения") Тогда
// Здесь мы можем либо установить Отказ = Истина (но тогда документ не проведется),
// либо просто не выполнять логику обновления данных.
// Но платформа все равно захочет записать пустой набор при замещении.
// Поэтому самый надежный способ — это Решение №1.
КонецЕсли;
КонецПроцедуры
Выясним причину, почему движения пропадают при нажатии кнопки "Отмена проведения". Как мы упоминали ранее, это связано со свойством документа Удаление движений. Если мы не хотим, чтобы при распроведении движения очищались, мы можем изменить это свойство на Не удалять автоматически. Однако это изменение затронет все регистры, по которым делает движения документ.
Если нам нужно сохранить движения только по одному конкретному регистру, а остальные очистить, то единственным верным путем остается использование подписки на событие ОбработкаУдаленияПроведения, где мы явно укажем:
Движения.ИмяВашегоРегистра.Записывать = Ложь;
Мы проанализировали ситуацию и выяснили, что наиболее архитектурно правильным и наименее трудозатратным способом является использование Подписок на события. Рассмотрим основные преимущества этого подхода:
Движения.Помните, что использование подчиненных регистров накопления подразумевает, что движения должны соответствовать состоянию регистратора. Оставляя "старые" движения в проведенном или, тем более, в распроведенном документе, вы можете столкнуться с ситуацией, когда остатки в отчетах не будут соответствовать реальности. Всегда тщательно проверяйте бизнес-логику таких решений.