При разработке конфигураций на платформе «1С:Предприятие 8» начинающие специалисты часто сталкиваются с ситуацией, когда при выборе номенклатуры в табличной части документа цена заполняется нулевым значением. Код при этом написан по всем правилам учебных пособий, ошибок компиляции нет, но вместо актуальной розничной цены программа упорно возвращает 0.
В этой статье мы подробно проанализируем, почему так происходит, разберем внутреннюю логику работы метода ПолучитьПоследнее(), выясним особенности работы с датами незаписанных документов и научимся правильно управлять осью времени в 1С с помощью объектов МоментВремени и Граница.
Рассмотрим исходный код, который часто вызывает проблемы у разработчиков:
&НаКлиенте
Процедура ПереченьНоменклатурыНоменклатураПриИзменении(Элемент)
СтрокаТабличнойЧасти = Элементы.ПереченьНоменклатуры.ТекущиеДанные;
// Получение цены с сервера
СтрокаТабличнойЧасти.Цена = РаботаСоСправочниками.РозничнаяЦена(Объект.Дата, СтрокаТабличнойЧасти.Номенклатура);
КонецПроцедуры
&НаСервере
Функция РозничнаяЦена(АктуальнаяДата, ЭлементНоменклатуры) Экспорт
Отбор = Новый Структура("Номенклатура", ЭлементНоменклатуры);
ЗначенияРесурсов = РегистрыСведений.Цены.ПолучитьПоследнее(АктуальнаяДата, Отбор);
Возврат ЗначенияРесурсов.Цена;
КонецФункции
Если в регистре сведений нет подходящих записей на указанную дату или дата запроса «меньше» даты самой первой записи регистра, метод ПолучитьПоследнее() не вызывает ошибку выполнения и не возвращает Неопределено.
Платформа 1С устроена следующим образом:
Получить(), ПолучитьПервое() и ПолучитьПоследнее() всегда возвращают объект типа Структура, содержащий ключи, соответствующие именам ресурсов регистра сведений.0, для строк — пустая строка, для булево — Ложь, для ссылочных типов — пустая ссылка).ЗначенияРесурсов.Цена успешно выполняется, но возвращает 0.Если вам критически важно различать ситуацию, когда цена действительно равна нулю, от ситуации, когда записи просто нет в базе данных, вместо метода ПолучитьПоследнее() следует использовать запрос к виртуальной таблице СрезПоследних. Запрос вернет пустой результат (ноль строк), что позволит вам перехватить отсутствие данных и выдать соответствующее предупреждение пользователю.
Проанализируем поведение реквизита Объект.Дата. Когда пользователь только что создал документ и еще не успел его записать, дата документа ведет себя специфически:
01.01.0001 00:00:00 (начало времен). Естественно, никаких цен на эту дату в базе нет, и метод возвращает нулевую цену.29.06.2026 00:00:00), а актуальные цены были внесены в регистр сегодня утром (например, в 09:00:00), то запрос цены на время 00:00:00 вернет вчерашнюю (или вообще нулевую) цену. Время документа в данном случае «отстает» от времени установки цен.Как решить эту проблему?
При получении цены для новой строки незаписанного документа на клиенте рекомендуется делать проверку. Если дата документа пустая или время указывает на начало дня, для получения актуальной цены безопаснее использовать текущую рабочую дату сеанса. Посмотрим на пример реализации такой проверки:
&НаКлиенте
Процедура ПереченьНоменклатурыНоменклатураПриИзменении(Элемент)
СтрокаТабличнойЧасти = Элементы.ПереченьНоменклатуры.ТекущиеДанные;
// Определяем рабочую дату для запроса цен
РабочаяДата = ?(ЗначениеЗаполнено(Объект.Дата), Объект.Дата, ТекущаяДатаСеанса());
СтрокаТабличнойЧасти.Цена = РаботаСоСправочниками.РозничнаяЦена(РабочаяДата, СтрокаТабличнойЧасти.Номенклатура);
КонецПроцедуры
Рассмотрим важный системный нюанс. Начинающие разработчики часто используют метод ТекущаяДата().
Использование метода ТекущаяДата() на клиенте возвращает системное время операционной системы локального компьютера пользователя. Если у пользователя на ПК сбиты часы или часовой пояс выставлен неверно, система подтянет некорректное время. Это повлечет за собой создание документов «задним числом» или «будущим временем», а также приведет к получению неактуальных цен из регистров.
Чтобы гарантировать стабильность работы системы, стандарты разработки 1С (включая требования БСП) настоятельно рекомендуют использовать метод ТекущаяДатаСеанса(). Данный метод запрашивает время у сервера приложений 1С:Предприятия, но автоматически корректирует его под часовой пояс конкретного пользовательского сеанса. Это полностью исключает зависимость логики программы от настроек часов на локальных компьютерах пользователей.
Когда документ записывается или проводится, нам может потребоваться получить цену строго на момент его проведения. Для этого в платформе предусмотрены специальные объекты управления временем.
Разберем различия между ними:
МоментВремени() — это объект, объединяющий в себе дату/время документа и уникальную ссылку на сам этот документ. Он используется для однозначного позиционирования документа на оси времени (особенно в пределах одной секунды). Если мы получаем данные из регистров на МоментВремени() нашего документа, платформа вернет нам данные непосредственно перед проведением этого документа. Собственные движения документа в этот срез не попадут. Это защищает от ситуации «самозацикливания».Граница() — это объект, позволяющий более гибко настроить точку на оси времени. Конструктор границы принимает момент времени и вид границы. Если мы используем вид границы ВидГраницы.Включая, платформа вернет нам данные на точку времени, находящуюся сразу после проведения нашего документа. Это означает, что все изменения, внесенные самим документом, будут учтены.Посмотрим, как выглядит программное создание объекта Граница:
// Создаем границу, включающую движения самого документа
ГраницаЦен = Новый Граница(МойДокумент.МоментВремени(), ВидГраницы.Включая);
// Получаем срез последних с учетом границы
ЗначенияРесурсов = РегистрыСведений.Цены.СрезПоследних(ГраницаЦен, Отбор);
Если вам нужно получить цену, установленную именно этим документом установки цен, используйте объект Граница с видом Включая. Если же документ во время проведения рассчитывает себестоимость или ищет цены, которые были актуальны до его проведения, используйте МоментВремени().
Проанализируем еще одну проблему исходного кода. Вызов серверной функции РозничнаяЦена при изменении каждой строки табличной части (событие НоменклатураПриИзменении) провоцирует так называемый round-trip — постоянные обращения клиента к серверу и обратно.
Если пользователь быстро вводит номенклатуру, сканирует штрихкоды или копирует строки в табличную часть, форма начнет заметно зависать, так как на каждую строку система будет тратить время на установку сетевого соединения с сервером и выполнение запроса к СУБД.
Как оптимизировать этот процесс:
Применение этих простых правил позволит сделать ваши интерфейсы отзывчивыми, а алгоритмы расчета цен — точными и устойчивыми к любым манипуляциям пользователей с системным временем.