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