Почему в 1С появляются фантомные валютные суммы по невалютным счетам и как это исправить?

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

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

Более того, при попытке зачесть аванс или провести документ КорректировкаДолга система заявляет, что по договору есть остаток долга, хотя стандартная ОСВ показывает чистый ноль. В этой статье мы подробно разберем, почему возникает эта скрытая проблема, как она связана с СУБД и какими способами её можно гарантированно устранить.

В чем скрытая суть проблемы: NULL против Пустой Ссылки

Для того чтобы понять природу этой ошибки, нам придется заглянуть "под капот" платформы 1С:Предприятие и СУБД (MS SQL Server или PostgreSQL). В плане счетов для каждого счета настраивается признак ведения валютного учета. Если этот флаг снят, то счет считается невалютным.

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

Проблема возникает тогда, когда в поле Валюта для невалютного счета вместо системного NULL записывается пустая ссылка конкретного типа данных — Справочники.Валюты.ПустаяСсылка() (физически в SQL это выглядит как уникальный идентификатор из одних нулей: 00000000-0000-0000-0000-000000000000).

Для SQL-сервера и оптимизатора запросов платформы 1С значения NULL и ПустаяСсылка — это принципиально разные сущности. Когда виртуальные таблицы остатков и оборотов пытаются рассчитать итоги, они группируют данные в разрезе измерений. В результате одна проводка ложится на измерение со значением NULL, а другая — со значением ПустаяСсылка. Мы получаем неприятные последствия: визуально пустые строки в отчетах, разъезжающиеся остатки и сбои в механизмах автозачета авансов.

Почему ломаются данные в базе?

Выясним основные причины, из-за которых в регистре бухгалтерии вместо правильного NULL прописывается пустая ссылка:

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

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

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

Для этого выполним следующие действия по шагам:

  1. Обязательно создадим резервную копию информационной базы (бэкап) перед началом любых манипуляций!
  2. Запустим базу данных в режиме Конфигуратор.
  3. Перейдем в меню Администрирование — Тестирование и исправление...
  4. Установим флаги напротив следующих проверок:
    • Реиндексация таблиц информационной базы (помогает обновить индексы таблиц итогов);
    • Проверка логической целостности информационной базы (именно она отвечает за поиск логических нестыковок в измерениях);
    • Пересчет итогов (актуализирует таблицы остатков после исправления данных).
  5. В группе переключателей "При наличии ошибок" выберем вариант Очищать объекты или ИСПРАВЛЯТЬ.
  6. Нажмем кнопку Выполнить и дождемся окончания процесса.

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

Способ 2. Программное исправление дефекта

Напишем небольшую внешнюю обработку, которая найдет все проблемные записи и перепишет их корректно. Идея проста: мы выберем все уникальные документы-регистраторы, у которых в проводках по невалютным счетам записана пустая ссылка вместо пустого значения, и перезапишем их наборы записей.

Рассмотрим пример кода, который решает эту задачу быстро и эффективно:


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

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

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

Проанализируем этот алгоритм: за счет того, что мы производим запись набора в стандартном режиме (свойство ОбменДанными.Загрузка не устанавливается в Истина), платформа принудительно задействует внутренний системный механизм валидации проводок. Она видит, что счет проводки не является валютным, и автоматически заменяет переданное значение пустой ссылки на корректный NULL.

Способ 3. Профилактика ошибок (доработка конфигурации)

Чтобы раз и навсегда обезопасить базу данных от появления пустых ссылок в измерениях регистра (особенно при регулярных обменах или частой работе обработок слияния дублей), мы можем внедрить превентивную защиту.

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


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

Этот простой шаг гарантирует, что даже если внешняя обработка или некорректно написанный обмен попытаются передать пустую ссылку в поле валюты для невалютного счета, платформа принудительно сбросит ее значение перед непосредственной отправкой SQL-запроса в базу данных.

Резюме

Фантомные остатки и пустые строки с валютой по счетам 60, 62 и другим невалютным счетам — это следствие подмены значения NULL на пустую ссылку типа Справочники.Валюты на уровне базы данных. Теперь мы знаем, как выявить эту проблему простым запросом, как исправить ее стандартными инструментами тестирования или с помощью небольшого скрипта, а также как защитить систему от ее повторного появления в будущем.

← На главную