При разработке механизмов интеграции, например, при загрузке данных из внешних систем типа iiko или r-keeper, программисты часто сталкиваются с ситуацией, когда код «не видит» уже созданный документ. В результате система создает дубликаты, а отладчик при попытке просмотреть результат запроса выдает загадочное сообщение «ошибка чтения значения». Разберем по шагам, почему это происходит и как построить надежный алгоритм поиска.
В системе 1С:Предприятие тип Дата — это не просто календарный день, это комбинация даты и времени с точностью до секунды. Рассмотрим условие в запросе, которое использовал автор темы:
| ГДЕ
| Приход.Дата = &Дата
Проанализируем ситуацию: если в параметре &Дата передается значение 01.10.2023 12:00:00, а документ в базе данных записан со временем 01.10.2023 12:00:05, запрос никогда не найдет этот документ. Разница всего в пять секунд делает условие равенства ложным. В автоматизированных системах время записи часто определяется моментом завершения транзакции или текущим временем сервера, поэтому точное совпадение — это скорее случайность, чем правило.
Решение: Для поиска документов в рамках одного дня всегда следует использовать проверку на вхождение в диапазон (от начала до конца дня). Изменим логику запроса, используя оператор МЕЖДУ или функцию НАЧАЛОПЕРИОДА.
// Вариант 1: Использование диапазона
Запрос.Текст =
"ВЫБРАТЬ Ссылка ИЗ Документ.ПриходнаяНакладная
|ГДЕ Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) И КОНЕЦПЕРИОДА(&Дата, ДЕНЬ)
|И Контрагент = &Контрагент";
// Вариант 2: Сравнение начал периодов
Запрос.Текст =
"ВЫБРАТЬ Ссылка ИЗ Документ.ПриходнаяНакладная
|ГДЕ НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)
|И Контрагент = &Контрагент";
Посмотрим на строку кода из примера: Приход.Дата = Дата - 6;. Здесь кроется критическая ошибка понимания типов. В языке 1С прибавление или вычитание числа из переменной типа Дата — это операция над секундами.
Вычитая 6, мы смещаем время документа всего на 6 секунд назад. Если автор хотел найти документ за «6 дней до...», расчет должен быть иным. В одном дне 86 400 секунд (24 * 60 * 60). Следовательно, для работы с днями нужно использовать соответствующие множители.
Проанализируем правильный подход к смещению дат:
// Если нужно вычесть 6 дней:
КоличествоДней = 6;
НужнаяДата = Дата - (КоличествоДней * 24 * 60 * 60);
// Но более современный и читаемый способ — использовать встроенную функцию:
НужнаяДата = ДобавитьМесяц(Дата, 0); // просто пример, для дней лучше ДобавитьМесяц не использовать
// В 8.3 рекомендуется:
НужнаяДата = НачалоДня(Дата) - 6 * 86400;
Когда мы останавливаемся на точке останова и видим в Выборка.Ссылка ошибку чтения, это не всегда означает сбой платформы. Рассмотрим механизм работы объекта ВыборкаИзРезультатаЗапроса.
Выборка.Следующий() никаких данных в полях выборки нет.Следующий() вернет Ложь, и курсор не сдвинется на запись.Посмотрим на фрагмент кода автора:
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Приход = Выборка.Ссылка.ПолучитьОбъект();
Иначе
// Если мы здесь, значит Выборка.Следующий() = Ложь
// Попытка посмотреть Выборка.Ссылка в этой ветке выдаст "ошибка чтения"
КонецЕсли;
Выясним причину: если условие в Если не выполняется, значит запрос ничего не нашел. В блоке Иначе данных нет, и обращение к свойствам выборки нелегитимно. Это подтверждает, что проблема именно в условиях самого запроса (неверная дата, не найден контрагент или не совпал комментарий).
Автор использует условие Приход.Комментарий ПОДОБНО &Комм. Использование решетки # и сложных строк при обмене данными с iiko может быть коварным. Проанализируем возможные риски:
\n или пробелы, которые визуально не заметны, но ломают поиск.ПОДОБНО стоит всегда использовать спецсимвол % с обеих сторон строки, если вы не уверены в полном совпадении.В коде мы видим: Контрагент = Справочники.Контрагенты.НайтиПоКоду("1-00008969");. Это опасная практика для тиражных решений или интеграций. Если в рабочей базе код контрагента изменится хотя бы на один символ (или в начале добавится префикс узла), метод вернет ПустаяСсылка.
Если Контрагент.Пустая(), то запрос по условию Приход.Контрагент = &Контрагент будет искать накладные с пустым контрагентом, что, очевидно, не приведет к результату. Рекомендуем всегда добавлять проверку после поиска:
Если Контрагент.Пустая() Тогда
Сообщить("Внимание: Контрагент с кодом " + КодПоиска + " не найден в справочнике!");
Возврат Истина; // Прекращаем выполнение с ошибкой
КонецЕсли;
Чтобы ваш алгоритм работал стабильно, проанализируем и внедрим следующие изменения:
НачалоДня(ДатаПоиска) и КонецДня(ДатаПоиска).[Дата, Дата + 1 день].Соблюдение этих правил позволит избежать создания дублей и обеспечит корректную перезапись существующих документов при повторной загрузке данных.