Почему в 1С расходится контроль остатков с ОСВ и как исправить коллизию NULL и Неопределено в субконто?

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

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

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

Симптомы проблемы и их первичный анализ

Проанализируем типичную ситуацию. В стандартной конфигурации «Бухгалтерия предприятия» пользователь оформляет списание материалов. Перед этим он формирует ОСВ по 10 счету и видит отрицательный остаток по позиции, например, минус 1790 кг. Но при подборе номенклатуры или при проведении документа система сообщает, что доступно еще 1717 кг в «плюсе».

При этом выполняются следующие стандартные проверки, которые не дают результата:

  1. Проверка складов: Отбора по складам в ОСВ нет, склад в документе и отчете совпадает (проверено по внутреннему коду элемента).
  2. Проверка дублей номенклатуры: Номенклатура проверена по уникальному идентификатору (GUID) и коду — это одна и та же карточка.
  3. Тестирование и исправление (ТИИ): Проведен полный пересчет итогов и проверка целостности базы данных, но отчеты и поведение системы не изменились.
  4. Права доступа: Операции выполняются под учетной записью с полными правами, исключая влияние ограничений на уровне записей (RLS).

Для того чтобы докопаться до истины, нам потребуется обратиться к запросам и детально посмотреть, как виртуальные таблицы остатков платформы «1С:Предприятие» видят наши данные.

Техническая суть коллизии: NULL против Неопределено

Разберем по шагам, что происходит на уровне СУБД. В платформе 1С пустые аналитики (субконто) в регистрах бухгалтерии могут быть представлены тремя принципиально разными типами значений. Визуально в стандартных отчетах они выглядят одинаково — как пустая строка, но для платформы и SQL-сервера это совершенно разные сущности:

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

В результате остатки в базе данных раздваиваются:

В сумме (свернуто) они дают тот самый минус в ОСВ, который видит пользователь (-1790 кг). Однако алгоритм контроля остатков при проведении документа может обращаться к виртуальной таблице остатков с определенным отбором или приведением типов, из-за чего он «видит» только ветку с положительным остатком NULL и разрешает проведение.

Как выявить проблему с помощью запроса?

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


ВЫБРАТЬ
    ХозрасчетныйОстатки.Счет КАК Счет,
    ХозрасчетныйОстатки.Субконто1 КАК Номенклатура,
    ХозрасчетныйОстатки.Субконто2 КАК Склад,
    ХозрасчетныйОстатки.Субконто3 КАК Партия,
    ТИПЗНАЧЕНИЯ(ХозрасчетныйОстатки.Субконто3) КАК ТипСубконто3,
    ХозрасчетныйОстатки.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Остатки(
        &ТекущаяДата, 
        Счет = &Счет, 
        , 
        Субконто1 = &Номенклатура И Субконто2 = &Склад
    ) КАК ХозрасчетныйОстатки

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

  1. В первой строке поле «Партия» будет пустым, а в колонке «ТипСубконто3» вернется NULL с положительным количеством.
  2. Во второй строке поле «Партия» также будет визуально пустым, но в колонке «ТипСубконто3» вернется Неопределено (или пустая ссылка) с отрицательным количеством.

Основные причины появления NULL в субконто

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

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

Методы решения проблемы

Рассмотрим подробнее три практических способа устранения коллизии NULL и Неопределено в регистрах бухгалтерии.

Способ 1: Перепроведение документов-источников

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

Разберем по шагам:

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

Способ 2: Программное исправление записей регистра

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

Рассмотрим пример программного кода для такой обработки:


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

Функция СогласоватьИмяДокумента(Ссылка)
    Возврат Строка(Ссылка);
КонецФункции

Этот код выбирает все регистраторы, которые сформировали проводки с NULL в третьем субконто, считывает их наборы записей, производит безопасную замену значения на Неопределено на уровне встроенного языка 1С и записывает наборы обратно в базу данных.

Способ 3: Полный пересчет итогов с отключением и включением функционала

Если ТИИ через конфигуратор не помогает, это происходит из-за того, что физическая таблица итогов просто пересчитывает существующие "кривые" записи в таблице движений. Чтобы заставить платформу полностью пересоздать таблицы остатков с корректным приведением типов, можно использовать метод временного отключения итогов:

  1. Откройте обработку «Управление итогами» (в разделе Администрирование или через функции для технического специалиста).
  2. Полностью отключите использование итогов для регистра бухгалтерии «Хозрасчетный». В этот момент таблицы остатков в базе SQL будут физически удалены (останется только таблица движений).
  3. Запустите тестирование и исправление (ТИИ) с флагами «Проверка целостности базы данных» и «Реструктуризация таблиц».
  4. Вернитесь в «Управление итогами» и включите использование итогов обратно, установив период рассчитанных итогов на текущий месяц. Платформа будет вынуждена заново рассчитать все остатки по движениям, автоматически приводя пустые типы к единому значению Неопределено.

Предотвращение проблемы в будущем

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

Мы детально изучили неочевидную техническую проблему работы механизма остатков в 1С. Понимание разницы между NULL и Неопределено на физическом уровне хранения данных позволяет быстро локализовать подобные ошибки и успешно исправлять их даже в закрытых периодах без нарушения ведения учета.

← На главную