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