Как программно изменить счет учета в проводках через подписку на событие в 1С:Бухгалтерия 3.0?

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При переходе с устаревших конфигураций (например, 1С:Бухгалтерия предприятия 2.0) на современные редакции (БП 3.0) разработчики часто сталкиваются с необходимостью переносить старые доработки. Одной из классических задач является автоматическая подмена счетов учета в проводках документов в зависимости от свойств номенклатуры. Например, когда определенные материалы (допустим, автомобильные шины) при передаче в эксплуатацию должны учитываться не на стандартном забалансовом счете МЦ.04, а на кастомном счете МЦ.05.

В этой статье мы подробно разберем, как правильно реализовать такую задачу с помощью механизма подписок на события в 1С 8.3, проанализируем типичные ошибки начинающих разработчиков, связанные с получением данных и валидацией субконто, и выработаем оптимальное архитектурное решение.

Почему не работает простой запрос в обработке проведения?

Рассмотрим ситуацию, с которой часто начинают новички. Создается подписка на событие ОбработкаПроведения для документа ПередачаМатериаловВЭксплуатацию. Внутри обработчика пишется запрос, который выбирает номенклатуру из табличной части проводимого документа по его ссылке:


// НЕПРАВИЛЬНЫЙ ПОДХОД
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТабЧасть.Номенклатура
    |ИЗ
    |	Документ.ПередачаМатериаловВЭксплуатацию.ИнвентарьИХозяйственныеПринадлежности КАК ТабЧасть
    |ГДЕ
    |	ТабЧасть.Ссылка = &Ссылка";

Выясним причину, почему этот код неэффективен или вовсе может возвращать пустой результат. В момент выполнения транзакции проведения новые данные документа (если они были изменены в форме, но еще не записаны в базу данных физически) могут отсутствовать в таблицах базы данных, к которым обращается запрос по ссылке. Обращение к базе данных по ссылке &Ссылка в момент проведения — это лишнее чтение из СУБД, которое замедляет работу системы.

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

Проанализируем этот шаг на примере кода:


// Выгружаем только нужную колонку для оптимизации памяти
ТаблицаНоменклатуры = Источник.ИнвентарьИХозяйственныеПринадлежности.Выгрузить(,"Номенклатура");

Как правильно прочитать дополнительные реквизиты и сведения

В конфигурациях на базе Библиотеки стандартных подсистем (БСП) дополнительные свойства объектов могут храниться в двух разных местах:

  1. Дополнительные реквизиты — это табличная часть самого справочника Номенклатура.ДополнительныеРеквизиты.
  2. Дополнительные сведения — хранятся во внешнем регистре сведений РегистрСведений.ДополнительныеСведения.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВТ_Номенклатура.Номенклатура КАК Номенклатура
    |ПОМЕСТИТЬ ВременнаяТаблицаНоменклатуры
    |ИЗ
    |	&ТаблицаНоменклатуры КАК ВТ_Номенклатура
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ.Номенклатура КАК Номенклатура
    |ИЗ
    |	ВременнаяТаблицаНоменклатуры КАК ВТ
    |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДополнительныеСведения КАК ДопСведения
    |		ПО ВТ.Номенклатура = ДопСведения.Объект
    |ГДЕ
    |	ДопСведения.Свойство = &СвойствоПризнака
    |	И ДопСведения.Значение = ИСТИНА";

Запрос.УстановитьПараметр("ТаблицаНоменклатуры", ТаблицаНоменклатуры);
// Задаем свойство из Плана видов характеристик
СвойствоПоиск = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("СчетМЦ05 (Номенклатура)");
Запрос.УстановитьПараметр("СвойствоПризнака", СвойствоПоиск);

РезультатЗапроса = Запрос.Выполнить();

Решение проблемы несоответствия субконто

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


// Простой код, вызывающий ошибку валидации субконто
ТекДвижение.СчетДт = ПланыСчетов.Хозрасчетный.НайтиПоКоду("МЦ.05");

При записи такого набора программа выдаст ошибку вида: "Запись не верна! Вид субконто 'Работники организаций' не доступен для данной записи!".

Выясним причину этой ошибки. В регистре бухгалтерии 1С проводка хранит аналитику в виде коллекции субконто (СубконтоДт и СубконтоКт). При ручном изменении счета дебета или кредита платформа автоматически не очищает старые субконто. Если у старого счета (например, МЦ.04) было три субконто (Номенклатура, Партии, Работники организаций), а у нового счета (МЦ.05) настроено только два субконто (Номенклатура, Работники), система попытается записать старую аналитику на новый счет. Не найдя соответствия видов субконто на новом счете, платформа заблокирует запись транзакции.

Для решения этой проблемы необходимо программно очищать субконто измененной стороны проводки и заполнять их заново. Для этого лучше всего использовать стандартный безопасный метод БСП БухгалтерскийУчет.УстановитьСубконто().

Рассмотрим пример правильной замены счета и заполнения субконто:


НовыйСчет = ПланыСчетов.Хозрасчетный.НайтиПоКоду("МЦ.05");

// Сохраняем нужные значения аналитики перед очисткой
ЗначениеНоменклатура = ТекДвижение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура];
ЗначениеРаботник = ТекДвижение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.РаботникиОрганизаций];

// Меняем счет
ТекДвижение.СчетДт = НовыйСчет;

// Очищаем коллекцию субконто дебета, чтобы избавиться от несовместимых видов аналитики
ТекДвижение.СубконтоДт.Очистить();

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

Выбор события для подписки: Документ или Регистр Бухгалтерии?

Большинство разработчиков вешают логику корректировки проводок на подписку к событию документа ОбработкаПроведения. Проанализируем, почему в современных типовых конфигурациях (таких как БП 3.0) этот подход может привести к потере данных.

В БП 3.0 расчет себестоимости и окончательное формирование проводок часто выполняются отложенно (например, при запуске регламентной операции "Закрытие месяца"). В этот момент система может перезаписать набор записей регистра бухгалтерии программно, напрямую обращаясь к регистру и минуя стандартную процедуру проведения документа пользователем. Если ваша подписка привязана только к документу, при закрытии месяца ваши кастомные счета (МЦ.05) будут стерты и заменены обратно на предопределенные (МЦ.04).

Архитектурно верное решение: использовать подписку на событие ПередЗаписью для объекта РегистрБухгалтерииНаборЗаписей.Хозрасчетный.

Поскольку этот обработчик будет вызываться при записи абсолютно любых проводок в системе, нам нужно максимально быстро отсекать "чужие" документы, проверяя тип регистратора, чтобы избежать просадки производительности.

Итоговое готовое решение

Разберем готовый шаблон реализации в общем модуле. Создайте подписку на событие со следующими свойствами:

Код в общем модуле (модуль должен иметь свойства "Клиент (на сервере)", "Сервер", "Внешнее соединение"):


Процедура ХозрасчетныйПередЗаписью(Источник, Отказ, Замещение) Экспорт
    
    // 1. Быстрая проверка: работаем только с нужным типом документа-регистратора
    Регистратор = Источник.Отбор.Регистратор.Значение;
    Если ТипЗнч(Регистратор) <> Тип("ДокументСсылка.ПередачаМатериаловВЭксплуатацию") Тогда
        Возврат;
    КонецЕсли;
    
    // Если документ помечается на удаление или проводится отмена проведения - движения просто очищаются, выходим
    Если Источник.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Получаем объект документа для обращения к его табличным частям без запросов к БД по ссылке
    ДокументОбъект = Регистратор.ПолучитьОбъект();
    ТаблицаНоменклатуры = ДокументОбъект.ИнвентарьИХозяйственныеПринадлежности.Выгрузить(,"Номенклатура");
    
    Если ТаблицаНоменклатуры.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 3. Выявляем номенклатуру, у которой установлено дополнительное сведение "СчетМЦ05"
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ВТ_Номенклатура.Номенклатура КАК Номенклатура
        |ПОМЕСТИТЬ ВТ_Документа
        |ИЗ
        |	&ТаблицаНоменклатуры КАК ВТ_Номенклатура
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |	ВТ.Номенклатура КАК Номенклатура
        |ИЗ
        |	ВТ_Документа КАК ВТ
        |	ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДополнительныеСведения КАК ДопСведения
        |		ПО ВТ.Номенклатура = ДопСведения.Объект
        |ГДЕ
        |	ДопСведения.Свойство = &СвойствоПризнака
        |	И ДопСведения.Значение = ИСТИНА";
        
    Запрос.УстановитьПараметр("ТаблицаНоменклатуры", ТаблицаНоменклатуры);
    
    СвойствоПризнака = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("СчетМЦ05 (Номенклатура)");
    Запрос.УстановитьПараметр("СвойствоПризнака", СвойствоПризнака);
    
    СписокНоменклатурыМЦ05 = Новый Массив;
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        СписокНоменклатурыМЦ05.Добавить(Выборка.Номенклатура);
    КонецЦикла;
    
    // Если подходящей номенклатуры нет, завершаем работу
    Если СписокНоменклатурыМЦ05.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    СчетМЦ04 = ПланыСчетов.Хозрасчетный.НайтиПоКоду("МЦ.04");
    СчетМЦ05 = ПланыСчетов.Хозрасчетный.НайтиПоКоду("МЦ.05"); // Кастомный счет забалансового учета
    
    // 4. Обходим коллекцию движений в наборе записей и производим безопасную замену
    Для Каждого Движение Из Источник Цикл
        
        // Проверяем, что это проводка по дебету счета МЦ.04
        Если Движение.СчетДт = СчетМЦ04 Тогда
            
            // Получаем номенклатуру из аналитики дебета
            НоменклатураПроводки = Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура];
            
            // Если номенклатура входит в наш список "МЦ.05"
            Если СписокНоменклатурыМЦ05.Найти(НоменклатураПроводки) <> Неопределено Тогда
                
                // Сохраняем значения субконто перед очисткой
                ЗначениеНоменклатура = НоменклатураПроводки;
                ЗначениеРаботник = Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.РаботникиОрганизаций];
                
                // Меняем счет
                Движение.СчетДт = СчетМЦ05;
                
                // Очищаем старые субконто для исключения конфликтов валидации видов субконто
                Движение.SubkontoDt.Clear(); // Или Движение.СубконтоДт.Очистить();
                
                // Перезаполняем субконто через стандартный механизм БСП
                БухгалтерскийУчет.УстановитьСубконто(Движение.СчетДт, Движение.СубконтоДт, "Номенклатура", ЗначениеНоменклатура);
                БухгалтерскийУчет.УстановитьСубконто(Движение.СчетДт, Движение.СубконтоДт, "РаботникиОрганизаций", ЗначениеРаботник);
                
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;

КонецПроцедуры

Использование этого подхода гарантирует, что ваши изменения будут надежно сохранены при любых перерасчетах себестоимости и закрытии месяца в 1С:Бухгалтерии 3.0, а очистка коллекции субконто убережет от досадных системных ошибок проведения.

← На главную