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