В практике разработки на платформе «1С:Предприятие» иногда возникают ситуации, когда требования бизнеса кажутся идущими вразрез с базовой архитектурой системы. Типичный пример: создание элемента справочника (например, нового уникального договора, контракта или уникальной услуги) должно автоматически фиксировать доход в системе — то есть формировать движения по регистру накопления «Продажи» или «Доходы».
С точки зрения стандартов проектирования 1С, справочники представляют собой условно-постоянную нормативно-справочную информацию (НСИ) и не могут выступать в роли регистраторов для регистров накопления или бухгалтерии. Движения в системе всегда должны порождаться документами. В этой статье мы подробно разберем, как решить эту задачу правильно, не нарушая стабильности базы данных, и какие технические «костыли» существуют, если обойти жесткое требование бизнеса автоматизировать процесс не удается.
Прежде чем перейти к технической реализации, давайте проанализируем, почему попытка заставить справочник напрямую или косвенно влиять на регистры накопления вызывает столько критики со стороны опытных архитекторов. Рассмотрим ключевые проблемы такого подхода:
Ярким примером того, к чему приводят подобные архитектурные «эксперименты», поделился один из участников обсуждения. На старых версиях платформы (в частности, 1С 7.7) разработчики пытались обойти ограничения, заталкивая всю бизнес-логику и детальные расчеты в один мега-документ или справочник, сериализуя таблицы в текстовые строки неограниченной длины. Итог всегда один — полная невозможность построения быстрой аналитической отчетности и колоссальные затраты на рефакторинг системы.
Тем не менее задачи бизнеса требуют решений. Рассмотрим два основных варианта реализации: архитектурно правильный и автоматизированный (программный).
Этот подход полностью соответствует стандартам разработки фирмы «1С». Мы разделяем нормативно-справочную информацию и хозяйственную операцию.
Проанализируем этот сценарий по шагам:
Договоры или Услуги). Этот объект не содержит финансовых данных о продаже, а лишь описывает параметры сущности.РеализацияТоваровУслуг или специального документа ОтражениеДоходов).Этот способ гарантирует, что в системе всегда будет сохраняться прозрачная история: любой бухгалтер или аудитор сможет четко отследить, какой именно документ сделал движения и на каком основании он был создан. Если операцию нужно отменить, достаточно просто распровести документ.
Что делать, если заказчик категорически отказывается от ручного ввода документов и требует: «Я хочу просто нажать кнопку 'Записать' в справочнике, и доход должен сразу отразиться в отчетах»? В этом случае нам придется реализовать механизм программного создания и проведения скрытого (служебного) документа в транзакции записи справочника.
Чтобы реализовать этот механизм корректно и избежать ошибок ссылочной целостности (когда документ создается, но сам справочник еще не записан и не имеет постоянной ссылки в базе данных), необходимо использовать метод УстановитьСсылкуНового().
Рассмотрим подробнее программный код, который необходимо разместить в модуле объекта нашего справочника:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Нам нужно отражать доход только при создании НОВОГО элемента
Если ЭтоНовый() Тогда
// Предопределяем ссылку для нового элемента справочника,
// чтобы передать ее в создаваемый документ до фактической записи справочника
СсылкаНаНовыйЭлемент = ПолучитьСсылкуНового();
Если Не ЗначениеЗаполнено(СсылкаНаНовыйЭлемент) Тогда
СсылкаНаНовыйЭлемент = Справочники.Договоры.ПолучитьСсылку();
УстановитьСсылкуНового(СсылкаНаНовыйЭлемент);
КонецЕсли;
// Создаем служебный документ, который выступит регистратором доходов
НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
// Заполняем реквизиты документа. Передаем нашу зарезервированную ссылку
НовыйДокумент.Договор = СсылкаНаНовыйЭлемент;
НовыйДокумент.Организация = Справочники.Организации.ОсновнаяОрганизация;
// Заполняем сумму, номенклатуру и другие необходимые реквизиты...
Попытка
// Проводим документ. Поскольку мы находимся внутри транзакции записи справочника,
// проведение документа также войдет в эту общую транзакцию.
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
// Записываем ссылку на созданный документ в реквизит нашего справочника
// для обеспечения перекрестной связи объектов
ЭтотОбъект.СвязанныйДокументДохода = НовыйДокумент.Ссылка;
Исключение
// Если документ не провелся (например, не хватило прав или остатков),
// отменяем запись самого справочника
Отказ = Истина;
ЗаписатьЗаписьЛогаРегистрации("Ошибка автоматического проведения дохода: " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
ВызватьИсключение "Не удалось зарегистрировать доход по договору. Запись отменена! Ошибка: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Давайте проанализируем, как работает этот алгоритм:
ПередЗаписью система проверяет, является ли элемент новым.ПолучитьСсылкуНового() мы проверяем, подготовлена ли ссылка. Если нет — генерируем ее принудительно через метод ПолучитьСсылку() менеджера справочника и фиксируем через УстановитьСсылкуНового().Проведение. Все движения по регистрам формируются автоматически.СвязанныйДокументДохода записывается ссылка на созданный документ. После этого транзакция успешно завершается, и в базе данных одновременно появляются и элемент справочника, и проведенный документ с движениями.Использование программного обхода накладывает на разработчика обязанность контролировать дальнейший жизненный цикл объектов. Если пользователь решит пометить элемент справочника на удаление или изменить его ключевые реквизиты (например, сумму или контрагента), вам придется отразить эти изменения в связанном документе.
Для этого в событие ПередЗаписью (уже для существующих элементов) необходимо добавить проверку изменений реквизитов и при необходимости программно перепроводить или отменять проведение связанного документа:
Если Не ЭтоНовый() Тогда
// Проверяем, изменились ли важные реквизиты справочника
СсылкаНаДокумент = ЭтотОбъект.СвязанныйДокументДохода;
Если ЗначениеЗаполнено(СсылкаНаДокумент) Тогда
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
// Если справочник пометили на удаление, отменяем проведение документа
Если ЭтотОбъект.ПометкаУдаления И Не ДокументОбъект.ПометкаУдаления Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
ДокументОбъект.УстановитьПометкуУдаления(Истина);
ИначеЕсли Не ЭтотОбъект.ПометкаУдаления И ДокументОбъект.ПометкаУдаления Тогда
ДокументОбъект.УстановитьПометкуУдаления(Ложь);
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Подводя итог, можно сказать: если у вас есть возможность убедить бизнес-аналитиков или заказчика использовать стандартный ввод документа на основании справочника — сделайте это. Это убережет систему от блокировок и спасет вас от написания сложного кода поддержки связанных объектов. Если же автоматизация критически важна, используйте предложенную схему со скрытым документом-регистратором и обязательным контролем транзакций.