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