Как предотвратить ввод некорректных дат и ошибки обмена данными в 1С?

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

Ошибки ввода дат пользователями — одна из распространенных причин сбоев в работе механизмов обмена данными (РИБ, универсальный обмен в формате EnterpriseData, планы обмена) и аварийного завершения транзакций. Опечатка в годе (например, 0024 вместо 2024 или 2160 вместо 2016) может привести к фатальной ошибке на стороне СУБД или срыву регламентных заданий обмена. Разберем природу этой проблемы и рассмотрим практические варианты ее решения.

Причины возникновения проблемы и ограничения СУБД

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

  1. Ограничения типа данных в MS SQL Server: стандартный тип DATETIME в MS SQL Server поддерживает диапазон дат только от 01.01.1753 до 31.12.9999. Если в базе 1С смещение дат установлено в 0, любая попытка записать дату ранее 1753 года приведет к исключению СУБД SqlDateTime overflow.
  2. Разница настроек между узлами: если в центральной базе или источнике используется СУБД PostgreSQL (где нет такого жесткого ограничения) либо база на MS SQL создана со смещением дат 2000, то документ успешно запишется. Однако при передаче данных в приемник с нулевым смещением произойдет сбой загрузки.
  3. Сбои при выгрузке и загрузке файлов выгрузки: при формировании файла выгрузки информационной базы (.dt) данные сохраняются в их исходном виде. При последующей загрузке такого файла в базу MS SQL с нулевым смещением процесс завершится аварией на этапе наполнения таблиц.

Вариант 1. Универсальная валидация дат через подписку на событие ОбработкаПроверкиЗаполнения

Рассмотрим наиболее правильный с точки зрения платформы 1С способ контроля — создание общего модуля и подписки на событие ОбработкаПроверкиЗаполнения для справочников, документов и регистров сведений. Этот подход не позволяет записать некорректные данные из пользовательского интерфейса, но пропускает загрузку в режиме обмена, если это необходимо.

Разберем по шагам реализацию процедуры проверки:


Процедура ПроверкаЗаполненияДатыОбработкаПроверкиЗаполнения(Источник, Отказ, ПроверяемыеРеквизиты) Экспорт
    
    // Если выполняется загрузка данных при обмене, пропускаем стандартную интерактивную проверку
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    ТекущаяДата = ТекущаяДата();
    
    // Задаем границы допустимого интервала дат
    МинимальнаяДата = ДобавитьМесяц(ТекущаяДата, -1200); // 100 лет назад
    МаксимальнаяДата = ДобавитьМесяц(ТекущаяДата, 120);   // 10 лет вперед
    НулеваяДата = '00010101';
    
    МетаданныеИсточника = Источник.Метаданные();
    
    // 1. Проверка документов и справочников
    Если Метаданные.Документы.Содержит(МетаданныеИсточника) Или Метаданные.Справочники.Содержит(МетаданныеИсточника) Тогда
        
        МассивПроверяемыхРеквизитов = Новый Массив;
        
        // Для документов обязательно проверяем стандартный реквизит Дата
        Если Метаданные.Документы.Содержит(МетаданныеИсточника) Тогда
            МассивПроверяемыхРеквизитов.Добавить("Дата");
        КонецЕсли;
        
        // Собираем реквизиты шапки с типом Дата
        Для Каждого Реквизит Из МетаданныеИсточника.Реквизиты Цикл
            Если Реквизит.Тип.СодержитТип(Тип("Дата")) Тогда
                МассивПроверяемыхРеквизитов.Добавить(Реквизит.Имя);
            КонецЕсли;
        КонецЦикла;
        
        // Проверяем значения в шапке объекта
        Для Каждого ИмяРеквизита Из МассивПроверяемыхРеквизитов Цикл
            ЗначениеРеквизита = Источник[ИмяРеквизита];
            Если ЗначениеЗаполнено(ЗначениеРеквизита) И ТипЗнч(ЗначениеРеквизита) = Тип("Дата") Тогда
                Если ЗначениеРеквизита > МаксимальнаяДата Тогда
                    ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Указана дата больше допустимой: " + Формат(МаксимальнаяДата, "ДФ=dd.MM.yyyy"), Источник, ИмяРеквизита, , Отказ);
                ИначеЕсли ЗначениеРеквизита < МинимальнаяДата И ЗначениеРеквизита <> НулеваяДата Тогда
                    ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Указана дата меньше допустимой: " + Формат(МинимальнаяДата, "ДФ=dd.MM.yyyy"), Источник, ИмяРеквизита, , Отказ);
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
        
        // Проверяем табличные части
        Для Каждого ТабЧасть Из МетаданныеИсточника.ТабличныеЧасти Цикл
            МассивРеквизитовТЧ = Новый Массив;
            Для Каждого РеквизитТЧ Из ТабЧасть.Реквизиты Цикл
                Если РеквизитТЧ.Тип.СодержитТип(Тип("Дата")) Тогда
                    МассивРеквизитовТЧ.Добавить(РеквизитТЧ.Имя);
                КонецЕсли;
            КонецЦикла;
            
            // Если в ТЧ нет колонок с типом Дата, перебор строк не выполняется
            Если МассивРеквизитовТЧ.Количество() = 0 Тогда
                Продолжить;
            КонецЕсли;
            
            Для Каждого СтрокаТЧ Из Источник[ТабЧасть.Имя] Цикл
                Для Каждого ИмяРеквизитаТЧ Из МассивРеквизитовТЧ Цикл
                    ЗначениеРеквизита = СтрокаТЧ[ИмяРеквизитаТЧ];
                    Если ЗначениеЗаполнено(ЗначениеРеквизита) И ТипЗнч(ЗначениеРеквизита) = Тип("Дата") Тогда
                        Если ЗначениеРеквизита > МаксимальнаяДата Тогда
                            ОбщегоНазначенияКлиентСервер.СообщитьПользователю("В строке " + СтрокаТЧ.НомерСтроки + " ТЧ " + ТабЧасть.Синоним + " указана некорректная дата", Источник, , , Отказ);
                        ИначеЕсли ЗначениеРеквизита < МинимальнаяДата И ЗначениеРеквизита <> НулеваяДата Тогда
                            ОбщегоНазначенияКлиентСервер.СообщитьПользователю("В строке " + СтрокаТЧ.НомерСтроки + " ТЧ " + ТабЧасть.Синоним + " указана дата ранее допустимой", Источник, , , Отказ);
                        КонецЕсли;
                    КонецЕсли;
                КонецЦикла;
            КонецЦикла;
        КонецЦикла;
        
    КонецЕсли;
    
КонецПроцедуры

Вариант 2. Контроль дат перед записью объекта

Проанализируем альтернативный вариант проверки непосредственно в обработчике события ПередЗаписью. Этот способ актуален, когда необходимо жестко заблокировать запись объекта даже при программном создании или в процессе выгрузки данных в распределенную информационную базу (РИБ).

Посмотрим на пример универсальной функции, проверяющей дату относительно установленной границы (смещения узла РИБ):


Функция ПроверитьКорректностьДатОбъекта(Объект, ГраничнаяДата) Экспорт
    
    Если НЕ ЗначениеЗаполнено(ГраничнаяДата) Тогда
        Возврат Истина;
    КонецЕсли;
    
    МетаданныеОбъекта = Объект.Метаданные();
    
    // Проверка реквизитов шапки
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        Значение = Объект[Реквизит.Имя];
        Если ТипЗнч(Значение) = Тип("Дата") И ЗначениеЗаполнено(Значение) Тогда
            Если Значение < ГраничнаяДата Тогда
                ВызватьИсключение "Реквизит '" + Реквизит.Синоним + "' содержит недопустимую дату: " + Значение;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    // Проверка табличных частей
    Для Каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
        ТаблицаОбъекта = Объект[ТЧ.Имя];
        Если ТаблицаОбъекта.Количество() = 0 Тогда
            Продолжить;
        КонецЕсли;
        
        Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
            Если РеквизитТЧ.Тип.СодержитТип(Тип("Дата")) Тогда
                Для Каждого СтрокаТЧ Из ТаблицаОбъекта Цикл
                    Значение = СтрокаТЧ[РеквизитТЧ.Имя];
                    Если ТипЗнч(Значение) = Тип("Дата") И ЗначениеЗаполнено(Значение) Тогда
                        Если Значение < ГраничнаяДата Тогда
                            ВызватьИсключение "В ТЧ '" + ТЧ.Синоним + "' строки " + СтрокаТЧ.НомерСтроки + 
                                              " реквизит '" + РеквизитТЧ.Синоним + "' содержит недопустимую дату: " + Значение;
                        КонецЕсли;
                    КонецЕсли;
                КонецЦикла;
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;
    
    Возврат Истина;
    
КонецФункции

Оптимизация проверки больших табличных частей и кэширование метаданных

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

  1. Предварительный отбор реквизитов даты: нельзя проверять тип каждого реквизита во внутреннем цикле по строкам табличной части. Сначала формируем список только тех колонок, которые имеют тип Дата, и только по ним выполняем обход строк.
  2. Использование выгрузки колонок: для табличных частей объемом в десятки тысяч строк перебор строк в цикле встроенного языка можно заменить выгрузкой колонки в массив через ТабличнаяЧасть.ВыгрузитьКолонку("ИмяРеквизита"), что выполняется на уровне платформы значительно быстрее.
  3. Кэширование состава метаданных: получение состава реквизитов через Метаданные при каждой записи создает дополнительную нагрузку. Рекомендуется сохранять структуру проверяемых полей объектов в модуле с повторным использованием возвращаемых значений.

Вариант 3. Фильтрация и корректировка дат при обмене данными

Если в базе-источнике уже присутствуют объекты с аномальными датами, исправить их на лету можно непосредственно в механизмах интеграции:

  1. В правилах конвертации данных (КД 2 / КД 3): в обработчиках событий ПередВыгрузкойСвойства или ПослеЗагрузкиОбъекта проверяем значение даты. Если дата меньше допустимой границы (например, меньше 01.01.1900) и не равна пустой дате '00010101', заменяем ее на пустую дату либо на дату документа. Это предотвращает падение пакета обмена.
  2. Валидация пакетов XDTO: при обмене через EnterpriseData некорректно сформированные даты приводят к ошибкам десериализации XML. Проверка и нормализация данных на стороне источника исключает аварийную остановку фоновых заданий обмена на стороне приемника.

Диагностика и исправление некорректных данных в существующей базе

Для обнаружения уже сохраненных аномальных дат в рабочей базе применяются следующие подходы:

  1. Тестирование и исправление: штатный запуск процедуры «Тестирование и исправление информационной базы» в режиме «Проверка логической целостности» находит битые ссылки и некорректные значения полей.
  2. Поиск запросом к базе данных: на стороне 1С можно написать обработку с динамическим формированием запросов ко всем таблицам метаданных, отбирающую записи с условием вида ГДЕ РеквизитДата < ДАТАВРЕМЯ(1900, 1, 1) И РеквизитДата <> ДАТАВРЕМЯ(1, 1, 1).
← На главную