Почему в регистре бухгалтерии появляется пустая ссылка вместо NULL и как это исправить?

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

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

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

В этой статье мы подробно разберем природу этого явления, выясним причины его возникновения в типовых конфигурациях (таких как «1С:Бухгалтерия предприятия 3.0») и рассмотрим практические методы решения проблемы без необходимости запускать многодневное Тестирование и исправление (ТИИ).

Теория вопроса: разница между NULL и пустой ссылкой в 1С

Для того чтобы понять суть проблемы, нам необходимо спуститься на уровень физической структуры баз данных СУБД (MS SQL Server, PostgreSQL и др.), с которыми работает платформа «1С:Предприятие 8».

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

Если для конкретного бухгалтерского счета в плане счетов выключен признак учета по подразделениям, или если счет не поддерживает налоговый учет, то платформа на уровне СУБД требует, чтобы в соответствующих полях таблиц базы данных было записано строго значение NULL. Это логично: если аналитика или вид учета не используются для данного счета, данных там физически быть не должно.

Однако пустая ссылка (например, пустой элемент справочника ПодразделенияОрганизаций) — это вполне конкретное, типизированное значение. Это уникальный идентификатор (GUID), состоящий из одних нулей (00000000-0000-0000-0000-000000000000). Для платформы 1С и СУБД значение NULL (отсутствие значения как такового) и пустая ссылка определенного типа — это абсолютно разные вещи.

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

Выясняем причины: как пустые ссылки попадают в регистр бухгалтерии?

Казалось бы, платформа «1С:Предприятие» должна жестко контролировать типы данных и автоматически приводить незаполненные значения к NULL там, где это необходимо по правилам плана счетов. В штатном режиме работы (при проведении документов пользователем) именно это и происходит. Платформа сама подставляет NULL в неактивные измерения и ресурсы.

Тогда откуда берутся пустые ссылки? Рассмотрим основные сценарии их появления:

  1. Режим загрузки данных (ОбменДанными.Загрузка = Истина). Это самая частая причина. При обмене данными, переносе проводок из внешних систем (например, из ЗУП в БП) или работе специализированных обработок записи в регистр бухгалтерии производятся напрямую. При установке свойства ОбменДанными.Загрузка = Истина платформа отключает большинство стандартных проверок и бизнес-правил, записывая в базу ровно то, что ей передали. Если в правилах конвертации или файле обмена для незаполненного подразделения была передана пустая ссылка, она запишется в базу «как есть», без преобразования в NULL.
  2. Использование обработок «Поиск и замена дублей». Зачастую в бухгалтерии запускают встроенные или сторонние обработки для слияния дублирующихся элементов справочников (контрагентов, договоров, подразделений). Если обработка написана некорректно или обходит стандартные механизмы проведения при перезаписи движений, она может принудительно записать пустую ссылку в регистр бухгалтерии вместо удаления значения (установки в NULL).
  3. Изменение настроек плана счетов «задним числом». Представим ситуацию: на определенном счете был включен учет по подразделениям. Пользователи активно вводили документы, в проводках заполнялось подразделение. Затем руководство решило отключить учет по подразделениям на этом счете. Бухгалтер снимает галочку в плане счетов. Платформа выполняет реструктуризацию таблиц, но из-за технических сбоев или особенностей релиза платформы старые заполненные значения в базе данных могут быть очищены некорректно — вместо NULL они преобразуются в пустые ссылки.
  4. Ошибки конфигураций и релизов платформы. В некоторых релизах платформы 1С существовали зарегистрированные ошибки, когда при обновлении конфигурации и последующей реструктуризации базы данных значения NULL в регистрах бухгалтерии самопроизвольно заменялись на пустые ссылки или нули (в ресурсах).

Диагностика: как быстро найти проблемные проводки

Для того чтобы локализовать проблему, нам потребуется написать простой запрос в Консоли запросов. Мы можем вычислить записи, у которых значение измерения не равно NULL, в то время как по правилам счета оно должно быть пустым.

Рассмотрим пример запроса для поиска некорректных записей по подразделениям на счетах, где учет по подразделениям выключен:


ВЫБРАТЬ
    ХозрасчетныйДвижения.Регистратор КАК Документ,
    ХозрасчетныйДвижения.Период КАК Период,
    ХозрасчетныйДвижения.НомерСтроки КАК НомерСтроки,
    ХозрасчетныйДвижения.СчетДт КАК СчетДт,
    ХозрасчетныйДвижения.ПодразделениеДт КАК ПодразделениеДт,
    ХозрасчетныйДвижения.СчетКт КАК СчетКт,
    ХозрасчетныйДвижения.ПодразделениеКт КАК ПодразделениеКт
ИЗ
    РегистрБухгалтерии.Хозрасчетный КАК ХозрасчетныйДвижения
ГДЕ
    (НЕ ХозрасчетныйДвижения.СчетДт.УчетПоПодразделениям И ХозрасчетныйДвижения.ПодразделениеДт <> ЗНАЧЕНИЕ(Справочник.ПодразделенияОрганизаций.ПустаяСсылка) И ХозрасчетныйДвижения.ПодразделениеДт ИНЕЙЛ(NULL))
    ИЛИ
    (НЕ ХозрасчетныйДвижения.СчетКт.УчетПоПодразделениям И ХозрасчетныйДвижения.ПодразделениеКт <> ЗНАЧЕНИЕ(Справочник.ПодразделенияОрганизаций.ПустаяСсылка) И ХозрасчетныйДвижения.ПодразделениеКт ИНЕЙЛ(NULL))

Этот запрос покажет нам все документы-регистраторы и конкретные строки проводок, которые нарушают логику метаданных. Вместо ключевого слова ИНЕЙЛ(NULL) (в синтаксисе запросов 1С это пишется как ЕСТЬ NULL) там записана пустая ссылка.

Способы решения проблемы

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

Способ 1: Тестирование и исправление (ТИИ)

Самый простой и очевидный способ, предлагаемый самой платформой. Нам необходимо запустить базу в режиме Конфигуратора, открыть меню Администрирование — Тестирование и исправление... и выбрать пункт Проверка логической целостности.

Во время этого процесса платформа сканирует все таблицы и при обнаружении несоответствий автоматически заменяет пустые ссылки на NULL. В окне сообщений вы увидите сотни строк вида: "Значение должно быть NULL. Сохранено значение NULL.".

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

Способ 2: Быстрый программный метод перезаписи наборов

Мало кто знает, но платформа 1С спроектирована очень умно. Если мы запишем набор записей регистра бухгалтерии в обычном режиме (когда признак ОбменДанными.Загрузка установлен в значение Ложь), платформа принудительно пересчитает все измерения и ресурсы. Она сама, на уровне своего внутреннего ядра, заменит все некорректные пустые ссылки на NULL в соответствии с текущими настройками плана счетов.

Для исправления ситуации нам не нужно перепроводить документы (что может изменить последовательность учета или занять много времени). Нам достаточно просто «прочитать» движения проблемного документа и «записать» их обратно в базу в обычном режиме. Проанализируем пример кода для такой обработки:


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

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

Запустив этот код в цикле по списку документов, полученных из нашего диагностического запроса, мы исправим базу за считанные минуты вместо дней ожидания ТИИ. Этот метод абсолютно безопасен, так как он не затрагивает другие таблицы и не меняет логику проведения — он лишь заставляет платформу привести типы данных к эталонному виду.

Способ 3: «Трюк» с планом счетов (для локальных ошибок)

Если проблема локализована на конкретном счете (например, вы точно знаете, что «поплыли» авансы по счету 60.01 или 62.01), можно прибегнуть к интересной хитрости, которая заставит платформу выполнить реструктуризацию по конкретному счету:

  1. Перейдите в режим «1С:Предприятие» (или в Конфигуратор, в зависимости от того, где у вас настраивается план счетов).
  2. Временно включите на проблемном счете признак учета, который сломался (например, установите галочку «Учет по подразделениям», даже если он там не нужен).
  3. Сохраните изменения. В этот момент база данных выполнит быструю точечную реструктуризацию таблиц движений только для этого счета.
  4. Снова отключите этот признак учета и сохраните настройки.

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

Профилактика: как избежать появления пустых ссылок в будущем?

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

Давайте посмотрим, как можно реализовать такую проверку программно. Создадим подписку на событие со следующим кодом в обработчике:


Процедура ПередЗаписьюДвиженийХозрасчетного(Источник, Отказ, Замещение) Экспорт
    
    // Если идет запись в режиме загрузки, платформа пропускает стандартные приведения типов.
    // Мы сделаем это принудительно на уровне кода.
    Если Источник.ОбменДанными.Загрузка Тогда
        
        Для Каждого Запись Из Источник Цикл
            
            // Проверяем Дебет
            Если ЗначениеЗаполнено(Запись.СчетДт) Тогда
                Если Не Запись.СчетДт.УчетПоПодразделениям И Запись.ПодразделениеДт <> Неопределено Тогда
                    Запись.ПодразделениеДт = Null;
                КонецЕсли;
            КонецЕсли;
            
            // Проверяем Кредит
            Если ЗначениеЗаполнено(Запись.СчетКт) Тогда
                Если Не Запись.СчетКт.УчетПоПодразделениям И Запись.ПодразделениеКт <> Неопределено Тогда
                    Запись.ПодразделениеКт = Null;
                КонецЕсли;
            КонецЕсли;
            
        КонецЦикла;
        
    КонецЕсли;

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

Данный код гарантирует, что даже если внешняя обработка или обмен попытаются записать пустую ссылку в измерение Подразделение по счету, на котором этот учет отключен, значение будет принудительно заменено на NULL до момента физической записи в базу данных СУБД. Это убережет вас от появления развернутого сальдо и сохранит целостность аналитики учета.

← На главную