Почему в Декларации по НДС у контрагентов в 1С:ERP не заполняются ИНН и КПП и как это исправить?

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

При переносе данных о контрагентах из сторонних конфигураций (например, из 1С:Бухгалтерии предприятия или самописных систем) в современные решения на базе ERP-платформы — 1С:ERP Управление предприятием, 1С:Комплексная автоматизация или 1С:Управление торговлей — разработчики и администраторы часто сталкиваются со странным поведением системы. В карточках контрагентов все реквизиты, включая ИНН и КПП, заполнены корректно. Однако при формировании регламентированной отчетности по НДС (Декларации по НДС, Книги покупок или Книги продаж) у этих контрагентов поля ИНН и КПП остаются пустыми.

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

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

В чем причина? Разбираем архитектуру хранения исторических данных

Чтобы понять суть проблемы, нам необходимо заглянуть «под капот» конфигураций, построенных на современной ERP-платформе. В отличие от более старых систем, в 1С:ERP реализована концепция хранения истории изменений реквизитов контрагента. В частности, это касается таких реквизитов, как КПП, Сокращенное наименование и Полное наименование.

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

Для реализации этой бизнес-логики разработчики фирмы 1С спроектировали справочник Контрагенты следующим образом:

  1. Текущие (актуальные на данный момент) значения ИНН и КПП хранятся в основных реквизитах шапки справочника: Контрагенты.ИНН и Контрагенты.КПП. Именно их мы видим, когда открываем карточку контрагента в режиме предприятия.
  2. Вся история изменений КПП записывается в подчиненную табличную часть справочника, которая называется ИсторияКПП. Каждая строка этой табличной части содержит сам КПП и дату, начиная с которой этот КПП считается действующим (поле ДействуетС).
  3. Аналогичным образом история изменения наименований пишется в подчиненные табличные части ИсторияПолныхНаименований и ИсторияСокращенныхНаименований.

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

Если при первоначальной программной загрузке данных из другой системы (через файлы XML, COM-соединение или EnterpriseData) был заполнен только основной реквизит шапки Объект.КПП, а табличная часть ИсторияКПП осталась пустой, то в Декларацию по НДС КПП не попадет, так как системе просто неоткуда взять исторические данные на дату документа.

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

Выясним, почему стандартные обработки группового изменения реквизитов или простая перезапись объекта кодом Объект.Записать() не исправляют ситуацию. Дело в том, что логика автоматического заполнения исторических табличных частей при изменении реквизитов шапки размещена разработчиками 1С **в модуле формы** справочника Контрагенты (в обработчиках событий формы, таких как ПередЗаписьюНаСервере или ПриЗаписиНаСервере), а не в модуле самого объекта справочника.

Проанализируем разницу в поведении системы при различных сценариях записи:

Разбор неэффективных способов решения проблемы

На форумах часто предлагают решать подобные проблемы «в лоб». Рассмотрим пример кода, который часто используют разработчики от безысходности (этот же метод упомянут в сообщении 5 исходной темы):


ОбъектСсылкаМассив = ПолучитьМассивПроблемныхКонтрагентов();
Для Каждого ОбъектСсылка Из ОбъектСсылкаМассив Цикл 
    Форма = ПолучитьФорму("Справочник.Контрагенты.ФормаОбъекта", Новый Структура("Ключ", ОбъектСсылка));
    Форма.Открыть();
    Форма.Записать();
    Форма.Закрыть();
КонецЦикла;

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

  1. Катастрофически низкая скорость выполнения. Открытие интерактивной формы требует создания клиентского контекста, инициализации всех элементов интерфейса, выполнения запросов при компоновке формы. Обработка даже 1000 контрагентов таким способом может занять часы.
  2. Зависимость от клиентского окружения. Данный код невозможно выполнить в фоновом задании на сервере, так как методы работы с формами (ПолучитьФорму, Открыть) доступны исключительно на клиенте.
  3. Риск утечки памяти и зависания системы. Поочередное открытие сотен тяжелых управляемых форм в цикле часто приводит к перегрузке клиентского сеанса и аварийному завершению работы платформы 1С.

Правильное программное решение: заполняем историю напрямую

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

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


// Шаг 1. Формируем запрос для поиска контрагентов с пустыми табличными частями истории
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Контрагенты.Ссылка КАК Ссылка,
    |	Контрагенты.КПП КАК КПП,
    |	Контрагенты.Наименование КАК Наименование,
    |	Контрагенты.НаименованиеПолное КАК НаименованиеПолное
    |ИЗ
    |	Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |	Контрагенты.КПП <> """"
    |	И НЕ Контрагенты.ЭтоГруппа
    |	И Контрагенты.Ссылка НЕ В
    |			(ВЫБРАТЬ РАЗЛИЧНЫЕ
    |				КонтрагентыИсторияКПП.Ссылка
    |			ИЗ
    |				Справочник.Контрагенты.ИсторияКПП КАК КонтрагентыИсторияКПП)";

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

НачатьТранзакцию();

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

Разбор ключевых особенностей кода

Рассмотрим подробнее важные нюансы представленного алгоритма:

Рекомендации на будущее: как избежать повторения проблемы

Чтобы при последующих переносах данных из внешних систем у вас не возникала аналогичная проблема с пустыми реквизитами в Декларации по НДС, рекомендуем скорректировать правила обмена:

Если вы используете правила конвертации данных (КД 2.0 или КД 3.0), обязательно добавьте обработчики событий при выгрузке или при загрузке данных контрагентов, которые будут принудительно инициализировать и заполнять строки в подчиненных табличных частях ИсторияКПП, ИсторияСокращенныхНаименований и ИсторияПолныхНаименований значениями по умолчанию. Это сэкономит ваше время и убережет от экстренного исправления ошибок перед сдачей квартальной отчетности.

← На главную