Каждый практикующий специалист 1С рано или поздно сталкивается с настоящей «черной магией» в бухгалтерском учете. Типичный сценарий: бухгалтер жалуется, что программа постоянно списывает авансы по одному и тому же контрагенту «в минус», хотя в Оборотно-сальдовой ведомости (ОСВ) общие остатки по счету равны нулю. При детальном анализе выясняется, что в базе образовалось так называемое развернутое сальдо. На остатках висят две записи: одна с положительным знаком, другая — с точно таким же отрицательным.
Самое интересное начинается при попытке раскопать аналитику. Внешне строки выглядят абсолютно одинаково, но виртуальные таблицы остатков СУБД упорно отказываются их схлопывать. Причина кроется на глубинном уровне платформы «1С:Предприятие 8»: в одном случае в измерении регистра записано значение NULL, а в другом — пустая ссылка (например, Справочники.ПодразделенияОрганизаций.ПустаяСсылка()). Для СУБД это два принципиально разных значения, что и приводит к двоению итогов.
В этой статье мы подробно разберем природу этого явления, выясним причины его возникновения в типовых конфигурациях (таких как «1С:Бухгалтерия предприятия 3.0») и рассмотрим практические методы решения проблемы без необходимости запускать многодневное Тестирование и исправление (ТИИ).
Для того чтобы понять суть проблемы, нам необходимо спуститься на уровень физической структуры баз данных СУБД (MS SQL Server, PostgreSQL и др.), с которыми работает платформа «1С:Предприятие 8».
Регистр бухгалтерии (например, Хозрасчетный) содержит измерения, ресурсы и реквизиты. Некоторые из этих полей имеют составной тип данных или могут быть отключены для конкретных счетов в плане счетов. Например, измерение Подразделение или ресурсы группы СуммаНУ (налоговый учет), ПР (постоянные разницы), ВР (временные разницы).
Если для конкретного бухгалтерского счета в плане счетов выключен признак учета по подразделениям, или если счет не поддерживает налоговый учет, то платформа на уровне СУБД требует, чтобы в соответствующих полях таблиц базы данных было записано строго значение NULL. Это логично: если аналитика или вид учета не используются для данного счета, данных там физически быть не должно.
Однако пустая ссылка (например, пустой элемент справочника ПодразделенияОрганизаций) — это вполне конкретное, типизированное значение. Это уникальный идентификатор (GUID), состоящий из одних нулей (00000000-0000-0000-0000-000000000000). Для платформы 1С и СУБД значение NULL (отсутствие значения как такового) и пустая ссылка определенного типа — это абсолютно разные вещи.
Когда мы формируем стандартные отчеты (например, ОСВ) без детальной группировки по подразделениям, платформа выводит агрегированные данные. На верхнем уровне эти суммы складываются, и мы видим красивый ноль. Но как только управление передается механизмам проведения документов (например, обработке зачета авансов), выполняются сложные запросы к виртуальным таблицам остатков. В этих запросах накладываются фильтры, убираются отрицательные строки, и в результате алгоритм видит «плюсовой» остаток аванса на записи с пустой ссылкой и пытается его зачесть, порождая новые некорректные движения.
Казалось бы, платформа «1С:Предприятие» должна жестко контролировать типы данных и автоматически приводить незаполненные значения к NULL там, где это необходимо по правилам плана счетов. В штатном режиме работы (при проведении документов пользователем) именно это и происходит. Платформа сама подставляет NULL в неактивные измерения и ресурсы.
Тогда откуда берутся пустые ссылки? Рассмотрим основные сценарии их появления:
ОбменДанными.Загрузка = Истина платформа отключает большинство стандартных проверок и бизнес-правил, записывая в базу ровно то, что ей передали. Если в правилах конвертации или файле обмена для незаполненного подразделения была передана пустая ссылка, она запишется в базу «как есть», без преобразования в NULL.NULL).NULL они преобразуются в пустые ссылки.NULL в регистрах бухгалтерии самопроизвольно заменялись на пустые ссылки или нули (в ресурсах).Для того чтобы локализовать проблему, нам потребуется написать простой запрос в Консоли запросов. Мы можем вычислить записи, у которых значение измерения не равно NULL, в то время как по правилам счета оно должно быть пустым.
Рассмотрим пример запроса для поиска некорректных записей по подразделениям на счетах, где учет по подразделениям выключен:
ВЫБРАТЬ
ХозрасчетныйДвижения.Регистратор КАК Документ,
ХозрасчетныйДвижения.Период КАК Период,
ХозрасчетныйДвижения.НомерСтроки КАК НомерСтроки,
ХозрасчетныйДвижения.СчетДт КАК СчетДт,
ХозрасчетныйДвижения.ПодразделениеДт КАК ПодразделениеДт,
ХозрасчетныйДвижения.СчетКт КАК СчетКт,
ХозрасчетныйДвижения.ПодразделениеКт КАК ПодразделениеКт
ИЗ
РегистрБухгалтерии.Хозрасчетный КАК ХозрасчетныйДвижения
ГДЕ
(НЕ ХозрасчетныйДвижения.СчетДт.УчетПоПодразделениям И ХозрасчетныйДвижения.ПодразделениеДт <> ЗНАЧЕНИЕ(Справочник.ПодразделенияОрганизаций.ПустаяСсылка) И ХозрасчетныйДвижения.ПодразделениеДт ИНЕЙЛ(NULL))
ИЛИ
(НЕ ХозрасчетныйДвижения.СчетКт.УчетПоПодразделениям И ХозрасчетныйДвижения.ПодразделениеКт <> ЗНАЧЕНИЕ(Справочник.ПодразделенияОрганизаций.ПустаяСсылка) И ХозрасчетныйДвижения.ПодразделениеКт ИНЕЙЛ(NULL))
Этот запрос покажет нам все документы-регистраторы и конкретные строки проводок, которые нарушают логику метаданных. Вместо ключевого слова ИНЕЙЛ(NULL) (в синтаксисе запросов 1С это пишется как ЕСТЬ NULL) там записана пустая ссылка.
Существует несколько вариантов исправления этой ситуации. Выбор зависит от размера информационной базы, доступного времени и ваших технических возможностей.
Самый простой и очевидный способ, предлагаемый самой платформой. Нам необходимо запустить базу в режиме Конфигуратора, открыть меню Администрирование — Тестирование и исправление... и выбрать пункт Проверка логической целостности.
Во время этого процесса платформа сканирует все таблицы и при обнаружении несоответствий автоматически заменяет пустые ссылки на NULL. В окне сообщений вы увидите сотни строк вида: "Значение должно быть NULL. Сохранено значение NULL.".
Недостатки метода: На больших базах (сотни гигабайт) процесс тестирования и исправления может занять несколько суток. На это время работу пользователей придется полностью остановить, что зачастую неприемлемо для бизнеса.
Мало кто знает, но платформа 1С спроектирована очень умно. Если мы запишем набор записей регистра бухгалтерии в обычном режиме (когда признак ОбменДанными.Загрузка установлен в значение Ложь), платформа принудительно пересчитает все измерения и ресурсы. Она сама, на уровне своего внутреннего ядра, заменит все некорректные пустые ссылки на NULL в соответствии с текущими настройками плана счетов.
Для исправления ситуации нам не нужно перепроводить документы (что может изменить последовательность учета или занять много времени). Нам достаточно просто «прочитать» движения проблемного документа и «записать» их обратно в базу в обычном режиме. Проанализируем пример кода для такой обработки:
Процедура ИсправитьПроводкиДокумента(ДокументСсылка) Экспорт
// Создаем набор записей регистра бухгалтерии с отбором по нашему регистратору
НаборЗаписей = РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
// Читаем текущие движения из базы данных
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() > 0 Тогда
// Важно: мы НЕ устанавливаем ОбменДанными.Загрузка = Истина.
// Запись должна пройти через штатные механизмы платформы.
Попытка
НаборЗаписей.Записать(Истина); // Параметр Истина означает замещение существующих записей
Сообщить("Успешно исправлены движения по документу: " + Строка(ДокументСсылка));
Исключение
Сообщить("Не удалось записать движения документа: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Запустив этот код в цикле по списку документов, полученных из нашего диагностического запроса, мы исправим базу за считанные минуты вместо дней ожидания ТИИ. Этот метод абсолютно безопасен, так как он не затрагивает другие таблицы и не меняет логику проведения — он лишь заставляет платформу привести типы данных к эталонному виду.
Если проблема локализована на конкретном счете (например, вы точно знаете, что «поплыли» авансы по счету 60.01 или 62.01), можно прибегнуть к интересной хитрости, которая заставит платформу выполнить реструктуризацию по конкретному счету:
При отключении признака платформа сама принудительно пробежится по всем записям этого счета в таблице регистра и перезапишет пустые ссылки на NULL. Это очень быстрый способ, не требующий написания кода.
Чтобы навсегда обезопасить базу данных от появления подобных ошибок при обменах или программной работе стороннего софта, мы можем внедрить превентивную проверку непосредственно в код конфигурации. Для этого отлично подойдет подписка на событие ПередЗаписью регистра бухгалтерии Хозрасчетный.
Давайте посмотрим, как можно реализовать такую проверку программно. Создадим подписку на событие со следующим кодом в обработчике:
Процедура ПередЗаписьюДвиженийХозрасчетного(Источник, Отказ, Замещение) Экспорт
// Если идет запись в режиме загрузки, платформа пропускает стандартные приведения типов.
// Мы сделаем это принудительно на уровне кода.
Если Источник.ОбменДанными.Загрузка Тогда
Для Каждого Запись Из Источник Цикл
// Проверяем Дебет
Если ЗначениеЗаполнено(Запись.СчетДт) Тогда
Если Не Запись.СчетДт.УчетПоПодразделениям И Запись.ПодразделениеДт <> Неопределено Тогда
Запись.ПодразделениеДт = Null;
КонецЕсли;
КонецЕсли;
// Проверяем Кредит
Если ЗначениеЗаполнено(Запись.СчетКт) Тогда
Если Не Запись.СчетКт.УчетПоПодразделениям И Запись.ПодразделениеКт <> Неопределено Тогда
Запись.ПодразделениеКт = Null;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Данный код гарантирует, что даже если внешняя обработка или обмен попытаются записать пустую ссылку в измерение Подразделение по счету, на котором этот учет отключен, значение будет принудительно заменено на NULL до момента физической записи в базу данных СУБД. Это убережет вас от появления развернутого сальдо и сохранит целостность аналитики учета.