Ошибки ввода дат пользователями — одна из распространенных причин сбоев в работе механизмов обмена данными (РИБ, универсальный обмен в формате EnterpriseData, планы обмена) и аварийного завершения транзакций. Опечатка в годе (например, 0024 вместо 2024 или 2160 вместо 2016) может привести к фатальной ошибке на стороне СУБД или срыву регламентных заданий обмена. Разберем природу этой проблемы и рассмотрим практические варианты ее решения.
Выясним причину, почему некорректная дата способна полностью заблокировать работу базы данных или механизмов обмена:
DATETIME в MS SQL Server поддерживает диапазон дат только от 01.01.1753 до 31.12.9999. Если в базе 1С смещение дат установлено в 0, любая попытка записать дату ранее 1753 года приведет к исключению СУБД SqlDateTime overflow.2000, то документ успешно запишется. Однако при передаче данных в приемник с нулевым смещением произойдет сбой загрузки..dt) данные сохраняются в их исходном виде. При последующей загрузке такого файла в базу MS SQL с нулевым смещением процесс завершится аварией на этапе наполнения таблиц.Рассмотрим наиболее правильный с точки зрения платформы 1С способ контроля — создание общего модуля и подписки на событие ОбработкаПроверкиЗаполнения для справочников, документов и регистров сведений. Этот подход не позволяет записать некорректные данные из пользовательского интерфейса, но пропускает загрузку в режиме обмена, если это необходимо.
Разберем по шагам реализацию процедуры проверки:
Процедура ПроверкаЗаполненияДатыОбработкаПроверкиЗаполнения(Источник, Отказ, ПроверяемыеРеквизиты) Экспорт
// Если выполняется загрузка данных при обмене, пропускаем стандартную интерактивную проверку
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
ТекущаяДата = ТекущаяДата();
// Задаем границы допустимого интервала дат
МинимальнаяДата = ДобавитьМесяц(ТекущаяДата, -1200); // 100 лет назад
МаксимальнаяДата = ДобавитьМесяц(ТекущаяДата, 120); // 10 лет вперед
НулеваяДата = '00010101';
МетаданныеИсточника = Источник.Метаданные();
// 1. Проверка документов и справочников
Если Метаданные.Документы.Содержит(МетаданныеИсточника) Или Метаданные.Справочники.Содержит(МетаданныеИсточника) Тогда
МассивПроверяемыхРеквизитов = Новый Массив;
// Для документов обязательно проверяем стандартный реквизит Дата
Если Метаданные.Документы.Содержит(МетаданныеИсточника) Тогда
МассивПроверяемыхРеквизитов.Добавить("Дата");
КонецЕсли;
// Собираем реквизиты шапки с типом Дата
Для Каждого Реквизит Из МетаданныеИсточника.Реквизиты Цикл
Если Реквизит.Тип.СодержитТип(Тип("Дата")) Тогда
МассивПроверяемыхРеквизитов.Добавить(Реквизит.Имя);
КонецЕсли;
КонецЦикла;
// Проверяем значения в шапке объекта
Для Каждого ИмяРеквизита Из МассивПроверяемыхРеквизитов Цикл
ЗначениеРеквизита = Источник[ИмяРеквизита];
Если ЗначениеЗаполнено(ЗначениеРеквизита) И ТипЗнч(ЗначениеРеквизита) = Тип("Дата") Тогда
Если ЗначениеРеквизита > МаксимальнаяДата Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Указана дата больше допустимой: " + Формат(МаксимальнаяДата, "ДФ=dd.MM.yyyy"), Источник, ИмяРеквизита, , Отказ);
ИначеЕсли ЗначениеРеквизита < МинимальнаяДата И ЗначениеРеквизита <> НулеваяДата Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Указана дата меньше допустимой: " + Формат(МинимальнаяДата, "ДФ=dd.MM.yyyy"), Источник, ИмяРеквизита, , Отказ);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Проверяем табличные части
Для Каждого ТабЧасть Из МетаданныеИсточника.ТабличныеЧасти Цикл
МассивРеквизитовТЧ = Новый Массив;
Для Каждого РеквизитТЧ Из ТабЧасть.Реквизиты Цикл
Если РеквизитТЧ.Тип.СодержитТип(Тип("Дата")) Тогда
МассивРеквизитовТЧ.Добавить(РеквизитТЧ.Имя);
КонецЕсли;
КонецЦикла;
// Если в ТЧ нет колонок с типом Дата, перебор строк не выполняется
Если МассивРеквизитовТЧ.Количество() = 0 Тогда
Продолжить;
КонецЕсли;
Для Каждого СтрокаТЧ Из Источник[ТабЧасть.Имя] Цикл
Для Каждого ИмяРеквизитаТЧ Из МассивРеквизитовТЧ Цикл
ЗначениеРеквизита = СтрокаТЧ[ИмяРеквизитаТЧ];
Если ЗначениеЗаполнено(ЗначениеРеквизита) И ТипЗнч(ЗначениеРеквизита) = Тип("Дата") Тогда
Если ЗначениеРеквизита > МаксимальнаяДата Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("В строке " + СтрокаТЧ.НомерСтроки + " ТЧ " + ТабЧасть.Синоним + " указана некорректная дата", Источник, , , Отказ);
ИначеЕсли ЗначениеРеквизита < МинимальнаяДата И ЗначениеРеквизита <> НулеваяДата Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("В строке " + СтрокаТЧ.НомерСтроки + " ТЧ " + ТабЧасть.Синоним + " указана дата ранее допустимой", Источник, , , Отказ);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Проанализируем альтернативный вариант проверки непосредственно в обработчике события ПередЗаписью. Этот способ актуален, когда необходимо жестко заблокировать запись объекта даже при программном создании или в процессе выгрузки данных в распределенную информационную базу (РИБ).
Посмотрим на пример универсальной функции, проверяющей дату относительно установленной границы (смещения узла РИБ):
Функция ПроверитьКорректностьДатОбъекта(Объект, ГраничнаяДата) Экспорт
Если НЕ ЗначениеЗаполнено(ГраничнаяДата) Тогда
Возврат Истина;
КонецЕсли;
МетаданныеОбъекта = Объект.Метаданные();
// Проверка реквизитов шапки
Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
Значение = Объект[Реквизит.Имя];
Если ТипЗнч(Значение) = Тип("Дата") И ЗначениеЗаполнено(Значение) Тогда
Если Значение < ГраничнаяДата Тогда
ВызватьИсключение "Реквизит '" + Реквизит.Синоним + "' содержит недопустимую дату: " + Значение;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Проверка табличных частей
Для Каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
ТаблицаОбъекта = Объект[ТЧ.Имя];
Если ТаблицаОбъекта.Количество() = 0 Тогда
Продолжить;
КонецЕсли;
Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
Если РеквизитТЧ.Тип.СодержитТип(Тип("Дата")) Тогда
Для Каждого СтрокаТЧ Из ТаблицаОбъекта Цикл
Значение = СтрокаТЧ[РеквизитТЧ.Имя];
Если ТипЗнч(Значение) = Тип("Дата") И ЗначениеЗаполнено(Значение) Тогда
Если Значение < ГраничнаяДата Тогда
ВызватьИсключение "В ТЧ '" + ТЧ.Синоним + "' строки " + СтрокаТЧ.НомерСтроки +
" реквизит '" + РеквизитТЧ.Синоним + "' содержит недопустимую дату: " + Значение;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Истина;
КонецФункции
При использовании универсальных алгоритмов в высоконагруженных системах возникает вопрос производительности. Рассмотрим методы оптимизации, позволяющие свести накладные расходы к минимуму:
Дата, и только по ним выполняем обход строк.ТабличнаяЧасть.ВыгрузитьКолонку("ИмяРеквизита"), что выполняется на уровне платформы значительно быстрее.Метаданные при каждой записи создает дополнительную нагрузку. Рекомендуется сохранять структуру проверяемых полей объектов в модуле с повторным использованием возвращаемых значений.Если в базе-источнике уже присутствуют объекты с аномальными датами, исправить их на лету можно непосредственно в механизмах интеграции:
ПередВыгрузкойСвойства или ПослеЗагрузкиОбъекта проверяем значение даты. Если дата меньше допустимой границы (например, меньше 01.01.1900) и не равна пустой дате '00010101', заменяем ее на пустую дату либо на дату документа. Это предотвращает падение пакета обмена.Для обнаружения уже сохраненных аномальных дат в рабочей базе применяются следующие подходы:
ГДЕ РеквизитДата < ДАТАВРЕМЯ(1900, 1, 1) И РеквизитДата <> ДАТАВРЕМЯ(1, 1, 1).