Почему документ «Поступление на расчетный счет» не формирует проводки после смены вида операции и как это исправить программно?

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

В процессе сопровождения «1С:Бухгалтерии» мы часто сталкиваемся с необходимостью массового изменения реквизитов в документах. Одна из самых коварных ситуаций возникает при попытке изменить Вид операции у банковских документов (например, «Поступление на расчетный счет» или «Списание с расчетного счета»). Чтобы предотвратить такие ошибки еще на этапе импорта выписок, можно настроить расширение для автоматического определения вида операции и аналитики при загрузке из банка.

Рассмотрим типичный сценарий: вы использовали стандартную обработку «Групповое изменение реквизитов» для смены вида операции (например, с «Оплата от покупателя» на «Возврат от поставщика» или наоборот). Реквизит изменился, но при перепроведении документов проводки исчезают полностью. Чтобы они появились снова, приходится заходить в каждый документ, вручную "передергивать" вид операции и записывать его.

В этой статье мы подробно разберем, почему так происходит, и реализуем программное решение для автоматической обработки тысяч документов, используя механизм Расширений и Внешних обработок.

Почему пропадают проводки?

Прежде чем писать код, давайте разберем механику работы документа. Документ «Поступление на расчетный счет» является полиморфным. Это значит, что состав его реквизитов, видимость полей и, самое главное, алгоритмы проведения зависят от выбранного значения в поле ВидОперации.

Когда мы меняем вид операции вручную в форме документа, срабатывает событие ПриИзменении. В этот момент программа:

  1. Очищает или скрывает ненужные табличные части.
  2. Заполняет счета учета (например, меняет 62.01 на 60.01 или 76.09) в соответствии с регистром сведений «Счета учета расчетов с контрагентами».
  3. Инициализирует новые обязательные поля, специфичные для выбранной операции.

При использовании «Группового изменения реквизитов» происходит прямая запись значения в реквизит объекта базы данных. События формы ПриИзменении при этом не срабатывают. В результате у вас в базе лежит документ с новым видом операции, но со старыми (или пустыми) счетами учета и несоответствующей структурой данных. Модуль проведения, видя это несоответствие, просто не формирует движения по регистрам. Для прямой корректировки движений можно использовать обработку для массовой замены счетов и аналитики в банковских проводках.

Варианты решения задачи

У нас есть два пути решения проблемы для большого массива документов (в примере автора темы — более 10 000):

  1. Эмуляция работы формы. Мы программно открываем форму каждого документа, меняем реквизит и принудительно вызываем процедуру, которая отрабатывает при интерактивном изменении.
  2. Работа с объектом. Мы анализируем логику заполнения документа в модуле объекта и воспроизводим её.

Рассмотрим первый вариант, так как он обсуждался в исходной теме и часто является более надежным способом «сделать как пользователь», не вдаваясь в дебри модуля объекта.

Реализация через эмуляцию формы

Главная сложность заключается в том, что процедура, обрабатывающая изменение вида операции в модуле формы, обычно не помечена ключевым словом Экспорт. Это означает, что мы не можем просто так вызвать её из внешней обработки.

Разберем пошаговый алгоритм решения, к которому пришли участники обсуждения.

Шаг 1. Создание Расширения конфигурации

Чтобы получить доступ к внутренней процедуре формы, нам нужно сделать её публичной. Для этого идеально подходит механизм Расширений. Нам не нужно снимать конфигурацию с поддержки.

Выполним следующие действия:

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

Наша цель — найти процедуру, отвечающую за смену вида операции. Обычно она называется ВидОперацииПриИзменении или вызывает процедуру вроде ОперацияИзменение. В типовых конфигурациях часто встречается код, который проверяет заполненность и вызывает серверную логику.

В расширении мы создадим новую экспортную процедуру, которая будет "оберткой" для типовой или просто продублирует вызов нужной логики.

Пример кода в модуле формы расширения:


&НаКлиенте
Процедура Расш_ВыполнитьИзменениеВидаОперации() Экспорт
    
    // Вызываем типовую процедуру, если она доступна, 
    // или имитируем её логику.
    // Часто типовая процедура называется ВидОперацииПриИзменении(Элемент)
    // Но так как Элемент нам передать сложно, смотрим, что внутри нее.
    
    // Обычно внутри вызов идет на сервер:
    ВидОперацииПриИзмененииСервер(); // Если такая процедура есть в форме
    
    // Или, как указано в обсуждении, процедура может называться иначе:
    // ОперацияИзменение(); 
    
    // Важно: Эта процедура должна быть видна в контексте формы.
    
КонецПроцедуры

Если типовая процедура ВидОперацииПриИзмененииСервер не является экспортной, но нам нужно её вызвать, мы можем попробовать использовать аннотацию &Вместо или просто скопировать необходимый кусок кода в нашу экспортную процедуру в расширении (хотя это менее красиво с точки зрения поддержки).

В примере из темы автор добавил экспортную процедуру, которая вызывает серверную логику:


// В расширении формы документа
&НаКлиенте
Процедура Расш_ПринудительноОбновитьВидОперации() Экспорт
    
    Если НЕ ЗначениеЗаполнено(Объект.ВидОперации) Тогда 
        Возврат; 
    КонецЕсли;
    
    // Вызов типовой серверной процедуры, которая пересчитывает счета и таблицы
    // Имя процедуры нужно подсмотреть в основной конфигурации
    ВидОперацииПриИзмененииСервер(); 
    
КонецПроцедуры

Шаг 2. Создание Внешней обработки

Теперь, когда у формы есть метод, который можно вызвать снаружи (Расш_ПринудительноОбновитьВидОперации), создадим обработку для перебора документов.

Обратите внимание: работа с формами происходит На Клиенте. Это значит, что обработка будет работать медленнее, чем чисто серверный код, но зато гарантирует корректность данных.

Код внешней обработки:


&НаКлиенте
Процедура ОбработатьДокументы(Команда)
    
    // Получаем список ссылок на документы (например, переданный с сервера или выбранный в обработке)
    МассивСсылок = ПолучитьСписокДокументовНаСервере();
    
    Для Каждого СсылкаНаДок Из МассивСсылок Цикл
        
        // Открываем форму программно, но не показываем её пользователю (хотя она создается в памяти)
        ПараметрыФормы = Новый Структура("Ключ", СсылкаНаДок);
        ФормаДокумента = ПолучитьФорму("Документ.ПоступлениеНаРасчетныйСчет.ФормаОбъекта", ПараметрыФормы);
        
        // Важно: Форма уже содержит объект с данными из базы.
        
        // 1. Устанавливаем нужный вид операции (если он еще не установлен)
        // Или просто "передергиваем" его, если нужно.
        // ФормаДокумента.Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперацийПоступлениеДенежныхСредств.ОплатаПокупателя");
        
        // 2. Вызываем нашу экспортную процедуру из расширения
        // Именно этот вызов пересчитает счета учета и табличные части!
        ФормаДокумента.Расш_ПринудительноОбновитьВидОперации();
        
        // 3. Записываем и проводим документ через форму
        // Это гарантирует, что данные объекта формы запишутся в базу
        Попытка
            ФормаДокумента.Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение));
            Сообщить("Обработан: " + СсылкаНаДок);
        Исключение
            Сообщить("Ошибка при записи: " + СсылкаНаДок + " | " + ОписаниеОшибки());
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

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

Важные нюансы и подводные камни

В процессе обсуждения мы выяснили несколько критических моментов:

  1. Передача параметров Клиент-Сервер. Автор темы столкнулся с проблемой передачи мутабельных значений. Именно поэтому лучше всего получать массив ссылок на сервере, возвращать его на клиент и затем в цикле на клиенте открывать формы. Пытаться передать саму форму или объект с клиента на сервер нельзя.
  2. Безопасный режим. При подключении внешней обработки убедитесь, что снят флажок «Безопасный режим». Иначе обработка может не иметь доступа к методам расширения.
  3. Скорость работы. Метод ПолучитьФорму довольно ресурсоемкий. Обработка 10 000 документов займет значительное время (возможно, несколько часов). Рекомендуем запускать обработку частями или в нерабочее время.
  4. Удаление расширения. После того как вы обработали все документы, расширение, созданное специально для этой задачи (с добавленным методом Экспорт), можно и нужно удалить, чтобы не засорять конфигурацию.

Альтернативный подход (без форм)

Если производительность критична, можно обойтись без форм, но это потребует более глубокого анализа кода конфигурации. В модуле объекта или менеджера документа часто существуют экспортные процедуры, выполняющие ту же работу.

Например, можно поискать методы вида:

Если такие методы доступны в объекте, код обработки упрощается и переносится полностью на сервер:


&НаСервере
Процедура ОбработатьНаСервере()
    
    Выборка = Документы.ПоступлениеНаРасчетныйСчет.Выбрать(ParametryOtbor);
    Пока Выборка.Следующий() Цикл
        
        ДокОбъект = Выборка.ПолучитьОбъект();
        ДокОбъект.ВидОперации = НужныйВидОперации;
        
        // Пытаемся найти и вызвать логику заполнения
        // В БП 3.0 часто используется механизм заполнения счетов через общие модули
        // Например: СчетаУчетаВДокументах.Заполнить(ДокОбъект);
        
        ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
        
    КонецЦикла;
    
КонецПроцедуры

Однако этот метод требует точного знания, какие именно процедуры нужно вызвать для конкретного вида документа. Метод с эмуляцией формы через расширение является более универсальным «ленивым» способом, гарантирующим результат.

Резюме

Если документы «Поступление на расчетный счет» перестали формировать проводки после массовой смены вида операции:

  1. Проблема в том, что не отработали обработчики события изменения реквизита.
  2. Самый надежный способ исправления — сымитировать действия пользователя.
  3. Для этого создайте временное Расширение, сделайте процедуру изменения вида операции в форме Экспортной.
  4. Напишите внешнюю обработку, которая перебирает документы, получает их форму, вызывает этот экспортный метод и записывает документ.
← На главную